# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import io
import os
import time
import re
import traceback
import sys
import requests

text_type = str

try:
    from kodi_six import xbmc, xbmcaddon, xbmcvfs
except ImportError:
    import xbmc
    import xbmcaddon
    import xbmcvfs

try:
    from requests.adapters import HTTPAdapter
    from urllib3.util.retry import Retry
except Exception:
    HTTPAdapter = None
    Retry = None

DEFAULT_TIMEOUT = 12
DEFAULT_USER_AGENT = (
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
    '(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'
)


def get_addon(addon=None):
    if addon is not None:
        return addon
    try:
        return xbmcaddon.Addon()
    except Exception:
        return None


def get_profile_dir(addon=None):
    addon = get_addon(addon)
    if addon is None:
        return ''
    return xbmcvfs.translatePath(addon.getAddonInfo('profile'))


def _path_exists(path):
    if not path:
        return False
    try:
        if xbmcvfs.exists(path):
            return True
    except Exception:
        pass
    try:
        return os.path.exists(path)
    except Exception:
        return False


def ensure_dir(path):
    """Cria diretório no perfil de forma tolerante entre plataformas Kodi.

    Em Android/iOS/tvOS e builds embarcadas, o caminho traduzido pelo Kodi é a
    autoridade. Tenta primeiro a VFS do Kodi e só então o filesystem Python.
    Nunca deixa falha de diretório derrubar a navegação; o chamador decide se
    precisa seguir sem cache local.
    """
    if not path:
        return path
    if _path_exists(path):
        return path
    try:
        if xbmcvfs.mkdirs(path):
            return path
    except Exception:
        pass
    try:
        if not os.path.exists(path):
            os.makedirs(path)
    except Exception:
        pass
    return path


PROFILE_DIR = ensure_dir(get_profile_dir())
LOG_FILE = os.path.join(PROFILE_DIR, 'oneplay_debug.log')


def safe_text(value):
    if value is None:
        return ''
    try:
        if isinstance(value, bytes):
            return value.decode('utf-8', 'replace')
    except Exception:
        pass
    try:
        if isinstance(value, text_type):
            return value
    except Exception:
        pass
    try:
        return str(value)
    except Exception:
        return repr(value)


def _to_text(value):
    return safe_text(value)


def get_setting_text(key, default='', addon=None):
    addon = get_addon(addon)
    if addon is None:
        return default
    try:
        value = addon.getSetting(key)
        return value if value not in (None, '') else default
    except Exception:
        return default


def get_setting_bool(key, default=False, addon=None):
    try:
        value = get_setting_text(key, 'true' if default else 'false', addon=addon)
        return safe_text(value).lower() == 'true'
    except Exception:
        return default


def set_setting_bool(key, value, addon=None):
    addon = get_addon(addon)
    if addon is None:
        return
    try:
        addon.setSetting(key, 'true' if value else 'false')
    except Exception:
        pass


def get_setting_int(key, default=0, addon=None):
    try:
        return int(get_setting_text(key, str(default), addon=addon))
    except Exception:
        return default


def get_setting_enum_value(key, values, default=None, addon=None):
    if not values:
        return default
    try:
        index = int(get_setting_text(key, '0', addon=addon))
        if 0 <= index < len(values):
            return values[index]
    except Exception:
        pass
    return values[0] if default is None else default


def debug_enabled(addon=None):
    """Retorna o interruptor global de manutenção do addon.

    ``mododebug`` não altera rede, proxy, cache ou player; ele apenas amplia
    os registros. Permanecer desligado é o comportamento normal de produção.
    """
    return get_setting_bool('mododebug', False, addon=addon)


# Campos de sessão e credenciais jamais devem aparecer no Kodi.log nem no
# oneplay_debug.log. A higienização é aplicada antes de qualquer escrita comum.
_SECRET_QUERY_RE = re.compile(
    r'(?P<key>(?:access_)?token|authorization|cookie|(?:x[-_])?(?:api[-_])?key|'
    r'x[-_]auth[-_]token|signature|sig|x[-_]amz[-_]signature|x[-_]goog[-_]signature|'
    r'password|passwd|pwd|username|user|email|session(?:id)?)'
    r'(?P<sep>\s*[=:]\s*)(?P<value>[^&#\s,;\]\)\}]+)',
    re.I
)
_SECRET_HEADER_RE = re.compile(
    r'(?P<key>authorization|cookie|set-cookie|x-api-key|x-auth-token|proxy-authorization)'
    r'(?P<sep>\s*:\s*)(?P<value>[^\r\n]+)',
    re.I
)
_URL_CREDENTIAL_RE = re.compile(r'(?P<scheme>https?://)(?P<user>[^\s/@:]+):(?P<password>[^\s/@]+)@', re.I)


def redact_log_text(value, max_length=6000):
    """Normaliza texto de auditoria preservando contexto, nunca segredos."""
    text = safe_text(value)
    try:
        text = _URL_CREDENTIAL_RE.sub(r'\g<scheme><credencial-oculta>@', text)
        text = _SECRET_HEADER_RE.sub(lambda match: '{}{}<oculto>'.format(match.group('key'), match.group('sep')), text)
        text = _SECRET_QUERY_RE.sub(lambda match: '{}{}<oculto>'.format(match.group('key'), match.group('sep')), text)
    except Exception:
        pass
    try:
        limit = max(256, int(max_length))
        if len(text) > limit:
            text = text[:limit] + '...<truncado>'
    except Exception:
        pass
    return text


def get_debug_log_path():
    return LOG_FILE


def _append_maintenance_line(message):
    try:
        ensure_dir(PROFILE_DIR)
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        with io.open(LOG_FILE, 'a', encoding='utf-8') as fh:
            fh.write(u'{} {}\n'.format(timestamp, redact_log_text(message)))
        return True
    except Exception:
        return False


def clear_debug_log():
    """Reinicia somente o log privado do addon, sem tocar em cache ou Kodi.log."""
    try:
        ensure_dir(PROFILE_DIR)
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        with io.open(LOG_FILE, 'w', encoding='utf-8') as fh:
            fh.write(u'{} [OnePlay DEBUG][Maintenance] log reiniciado pelo usuário\n'.format(timestamp))
        return True
    except Exception:
        return False


def _debug_traceback(component, context, exc=None, addon=None):
    """Escreve traceback somente quando o Debug completo está ligado."""
    if not debug_enabled(addon=addon):
        return
    if exc is None:
        try:
            exc = sys.exc_info()[1]
        except Exception:
            exc = None
    try:
        if exc is not None:
            trace_text = ''.join(traceback.format_exception(type(exc), exc, getattr(exc, '__traceback__', None)))
        else:
            trace_text = traceback.format_exc()
    except Exception:
        trace_text = ''
    if not trace_text or trace_text.strip() == 'NoneType: None':
        return
    for line in trace_text.rstrip().splitlines():
        if line.strip():
            log_debug('{} | traceback={}'.format(context or 'Falha', line), addon=addon, component=component)


def log(message, level=None, _capture_exception=True):
    if level is None:
        level = xbmc.LOGINFO
    clean_message = redact_log_text(message)
    text = '[OnePlay] {}'.format(clean_message)
    try:
        xbmc.log(text, level=level)
    except Exception:
        pass
    _append_maintenance_line(clean_message)
    # Muitos módulos antigos registram a exceção via log(...) dentro do except.
    # Aqui eles passam a ganhar traceback automático quando o Debug completo
    # estiver ligado, sem mudar a lógica de produção de nenhum módulo.
    try:
        active_exc = sys.exc_info()[1]
        warning_level = getattr(xbmc, 'LOGWARNING', 2)
        if _capture_exception and active_exc is not None and int(level) >= int(warning_level):
            _debug_traceback('Runtime', clean_message, active_exc)
    except Exception:
        pass


def log_debug(message, addon=None, component=None):
    """Linha detalhada global, emitida somente com Debug completo ativado."""
    if not debug_enabled(addon=addon):
        return
    if component:
        prefix = '[OnePlay DEBUG][{}] '.format(safe_text(component)[:80])
    else:
        prefix = '[OnePlay DEBUG] '
    formatted = prefix + redact_log_text(message)
    try:
        # LOGINFO garante visibilidade mesmo quando o debug global do Kodi não
        # está ligado. O interruptor é exclusivamente o do próprio addon.
        xbmc.log(formatted, level=xbmc.LOGINFO)
    except Exception:
        pass
    _append_maintenance_line(formatted)


def log_event(component, event, addon=None, **fields):
    """Evento estruturado e seguro para correlação de manutenção."""
    parts = ['evento={}'.format(redact_log_text(event, 160))]
    for key in sorted(fields):
        value = fields.get(key)
        if value in (None, ''):
            continue
        parts.append('{}={}'.format(redact_log_text(key, 48), redact_log_text(value, 320)))
    log_debug(' | '.join(parts), addon=addon, component=component)


def log_debug_traceback(component, context, exc=None, addon=None):
    """Expõe traceback apenas para diagnóstico, sem criar alerta de produção."""
    _debug_traceback(component, context, exc, addon=addon)


def log_exception(component, context, exc=None, addon=None, level=None):
    """Registra falha concisa sempre e traceback completo somente em Debug.

    O traceback também passa pela higienização, portanto URLs assinadas,
    cookies, tokens e credenciais que por acaso apareçam na exceção ficam
    mascarados antes de serem escritos.
    """
    if exc is None:
        try:
            exc = sys.exc_info()[1]
        except Exception:
            exc = None
    exc_name = exc.__class__.__name__ if exc is not None else 'ErroDesconhecido'
    log('[{}] {}: {}'.format(component or 'Addon', context or 'Falha', exc_name),
        level=level if level is not None else getattr(xbmc, 'LOGERROR', 4), _capture_exception=False)
    if not debug_enabled(addon=addon):
        return
    _debug_traceback(component or 'Addon', context or 'Falha', exc, addon=addon)
    if exc is None:
        log_debug('{} | detalhe={}'.format(context or 'Falha', exc_name), addon=addon, component=component)

def make_session(user_agent=None, retries=2):
    session = requests.Session()
    session.headers.update({'User-Agent': user_agent or DEFAULT_USER_AGENT})
    if HTTPAdapter and Retry:
        try:
            retry = Retry(
                total=retries,
                connect=retries,
                read=retries,
                backoff_factor=0.35,
                status_forcelist=[429, 500, 502, 503, 504],
                allowed_methods=frozenset(['GET', 'HEAD'])
            )
        except TypeError:
            retry = Retry(
                total=retries,
                connect=retries,
                read=retries,
                backoff_factor=0.35,
                status_forcelist=[429, 500, 502, 503, 504],
                method_whitelist=frozenset(['GET', 'HEAD'])
            )
        adapter = HTTPAdapter(max_retries=retry, pool_connections=4, pool_maxsize=8)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
    return session



def sync_legacy_setting_mirrors(addon=None):
    addon = get_addon(addon)
    if addon is None:
        return
    try:
        adult_enabled = get_setting_bool('tv_adult_protect', False, addon=addon)
        guard_value = get_setting_bool('tv_adult_protect_guard_state', adult_enabled, addon=addon)
        if guard_value != adult_enabled:
            set_setting_bool('tv_adult_protect_guard_state', adult_enabled, addon=addon)
    except Exception:
        pass
