# -*- coding: utf-8 -*-
"""Service leve do OnePlayVIP no startup do Kodi.

Mantém a limpeza conservadora de APKs e prepara o EPG XMLTV em background
quando o EPG está habilitado. A navegação do addon fica lendo o índice pronto,
sem baixar XMLTV pesado ao abrir categorias de canais.
Compatível com Kodi 19+ / Python 3.
"""
import hashlib
import os
import time

try:
    import xbmc
    import xbmcaddon
    import xbmcvfs
except Exception:
    xbmc = None
    xbmcaddon = None
    xbmcvfs = None

try:
    import requests
except Exception:
    requests = None

try:
    from resources.lib import epg_runtime, storage_runtime
    from resources.lib.server_config import DNS_LIST_B64, get_base_url as external_get_base_url, get_current_server_choice as external_get_current_server_choice
except Exception:
    epg_runtime = None
    storage_runtime = None
    DNS_LIST_B64 = []
    external_get_base_url = None
    external_get_current_server_choice = None

ADDON_ID = 'plugin.video.oneplayvip'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
HEADERS = {'User-Agent': USER_AGENT}
TRANSIENT_HTTP_STATUS = {429, 500, 502, 503, 504}

APK_DOWNLOAD_DIRS = [
    '/storage/emulated/0/Download',
    '/sdcard/Download',
    '/storage/self/primary/Download',
]
APK_FILENAMES = [
    'OnePlayAlpha.apk',
    'OnePlayIbo.apk',
    'OnePlayXtream.apk',
]

EPG_CACHE_DAYS = 1
EPG_TTL = EPG_CACHE_DAYS * 24 * 3600
EPG_INDEX_VERSION = 'oneplayvip_epg_xcpro_window_v3_service_watchdog'
EPG_INDEX_WINDOW_DAYS = 2
MAX_EPG_INDEX_FILES = 8
XMLTV_DEFAULT_OFFSET_SECS = -(3 * 3600)
EPG_BOUNDARY_TOLERANCE_SECS = 300
# Vigia conservador: verifica de hora em hora; download real continua diário/condicional.
EPG_SERVICE_CHECK_INTERVAL_SECS = 60 * 60
EPG_REFRESH_MARGIN_SECS = 2 * 3600
EPG_MIN_FUTURE_COVERAGE_SECS = 12 * 3600
EPG_MIN_REFRESH_INTERVAL_SECS = 60 * 60


def _log(message, level=None):
    try:
        if xbmc is not None:
            xbmc.log('[{}] {}'.format(ADDON_ID, message), level or xbmc.LOGDEBUG)
    except Exception:
        pass


def _addon():
    if xbmcaddon is None:
        return None
    try:
        return xbmcaddon.Addon(ADDON_ID)
    except Exception:
        try:
            return xbmcaddon.Addon()
        except Exception:
            return None


def _is_android():
    try:
        return bool(xbmc is not None and xbmc.getCondVisibility('System.Platform.Android'))
    except Exception:
        return False


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


def _delete_file(path):
    try:
        if xbmcvfs is not None and xbmcvfs.exists(path):
            return bool(xbmcvfs.delete(path))
    except Exception:
        pass
    try:
        if os.path.exists(path):
            os.remove(path)
            return True
    except Exception:
        pass
    return False


def cleanup_downloaded_apks_on_startup():
    # Fora do Android/Android TV não existe instalador APK; evita varredura e log desnecessário.
    if not _is_android():
        return []

    removed = []
    seen = set()
    for folder in APK_DOWNLOAD_DIRS:
        if not folder or folder in seen:
            continue
        seen.add(folder)
        for filename in APK_FILENAMES:
            path = os.path.join(folder, filename)
            if _path_exists(path) and _delete_file(path):
                removed.append(path)
    if removed:
        _log('Limpeza de APK no startup: {} arquivo(s) removido(s).'.format(len(removed)), getattr(xbmc, 'LOGINFO', None))
    return removed


def _safe_requests_get(url, **kw):
    if requests is None:
        raise RuntimeError('requests indisponível no Kodi')
    kw.setdefault('headers', HEADERS)
    kw.setdefault('timeout', 30)
    retries = max(0, int(kw.pop('retries', 2)))
    backoff = max(0.2, float(kw.pop('backoff', 1.0)))
    last_exc = None
    for attempt in range(retries + 1):
        try:
            r = requests.get(url, **kw)
            if r.status_code in TRANSIENT_HTTP_STATUS and attempt < retries:
                try:
                    r.close()
                except Exception:
                    pass
                time.sleep(backoff * (2 ** attempt))
                continue
            r.raise_for_status()
            return r
        except Exception as exc:
            last_exc = exc
            if attempt < retries:
                time.sleep(backoff * (2 ** attempt))
                continue
            raise last_exc


def _get_profile_dir(addon):
    try:
        return xbmcvfs.translatePath(addon.getAddonInfo('profile'))
    except Exception:
        return os.path.join(os.path.expanduser('~'), '.kodi', 'userdata', 'addon_data', ADDON_ID)


def _get_username(addon):
    try:
        return (addon.getSetting('username') or '').strip()
    except Exception:
        return ''


def _get_password(addon):
    try:
        return (addon.getSetting('password') or '').strip()
    except Exception:
        return ''


def _get_enable_epg(addon):
    try:
        return (addon.getSetting('enable_epg') or 'false').lower() == 'true'
    except Exception:
        return False


def _get_current_server_choice(addon):
    try:
        if external_get_current_server_choice:
            return external_get_current_server_choice(addon, dns_list=DNS_LIST_B64)
    except Exception:
        pass
    try:
        return max(0, int(addon.getSetting('server_choice') or '0'))
    except Exception:
        return 0


def _get_base_url(addon):
    try:
        if external_get_base_url:
            return (external_get_base_url(addon, dns_list=DNS_LIST_B64, log_fn=lambda message: _log(message, getattr(xbmc, 'LOGERROR', None))) or '').strip()
    except Exception:
        pass
    return ''


def _fingerprint(addon):
    base_url = (_get_base_url(addon) or '').strip().rstrip('/')
    username = _get_username(addon)
    password = _get_password(addon)
    raw = '{}|{}|{}'.format(base_url, username, password)
    return hashlib.sha1(raw.encode('utf-8')).hexdigest()


def _server_slot_scope_name(addon, server_idx=None):
    if storage_runtime is not None:
        try:
            return storage_runtime.server_slot_scope_name(lambda: _get_current_server_choice(addon), server_idx)
        except Exception:
            pass
    try:
        idx = _get_current_server_choice(addon) if server_idx is None else int(server_idx)
    except Exception:
        idx = 0
    if idx < 0:
        idx = 0
    return 'server_{}'.format(idx + 1)


def _server_slot_storage_path(profile_dir, addon, filename, server_idx=None):
    if storage_runtime is not None:
        try:
            return storage_runtime.server_slot_storage_path(profile_dir, filename, lambda: _get_current_server_choice(addon), server_idx)
        except Exception:
            pass
    return os.path.join(profile_dir, 'server_slot_storage', _server_slot_scope_name(addon, server_idx), filename)


def prepare_epg_xmltv_cache_on_startup(monitor=None):
    if epg_runtime is None or storage_runtime is None:
        _log('EPG service ignorado: runtime indisponível.', getattr(xbmc, 'LOGDEBUG', None))
        return False

    addon = _addon()
    if addon is None:
        return False
    if not _get_enable_epg(addon):
        return False

    username = _get_username(addon)
    password = _get_password(addon)
    base_url = _get_base_url(addon)
    if not (username and password and base_url):
        return False

    if monitor is not None:
        try:
            if monitor.abortRequested():
                return False
        except Exception:
            pass

    profile_dir = _get_profile_dir(addon)

    def ensure_profile_dir():
        return storage_runtime.ensure_profile_dir(profile_dir)

    def ensure_parent_dir(path):
        return storage_runtime.ensure_parent_dir(path)

    def load_json_file(path, default=None):
        return storage_runtime.load_json_file(path, default=default, profile_dir=profile_dir, log_fn=_log)

    def atomic_write_json(path, data, ensure_ascii=False, indent=None):
        return storage_runtime.atomic_write_json(path, data, profile_dir=profile_dir, ensure_ascii=ensure_ascii, indent=indent, log_fn=_log)

    def atomic_write_text(path, text, encoding='utf-8'):
        return storage_runtime.atomic_write_text(path, text, profile_dir=profile_dir, encoding=encoding)

    def server_slot_storage_path(filename, server_idx=None):
        return _server_slot_storage_path(profile_dir, addon, filename, server_idx)

    def epg_xml_storage_path():
        return server_slot_storage_path('epg_xmltv.xml')

    def epg_meta_storage_path():
        return server_slot_storage_path('epg_xmltv_meta.json')

    ctx = {
        'profile_dir': profile_dir,
        'username': username,
        'password': password,
        'epg_cache_days': EPG_CACHE_DAYS,
        'epg_ttl': EPG_TTL,
        'epg_index_version': EPG_INDEX_VERSION,
        'epg_index_window_days': EPG_INDEX_WINDOW_DAYS,
        'max_epg_index_files': MAX_EPG_INDEX_FILES,
        'xmltv_default_offset_secs': XMLTV_DEFAULT_OFFSET_SECS,
        'epg_boundary_tolerance_secs': EPG_BOUNDARY_TOLERANCE_SECS,
        'epg_refresh_margin_secs': EPG_REFRESH_MARGIN_SECS,
        'epg_min_future_coverage_secs': EPG_MIN_FUTURE_COVERAGE_SECS,
        'epg_min_refresh_interval_secs': EPG_MIN_REFRESH_INTERVAL_SECS,
        'honor_refresh_request': True,
        'log_fn': _log,
        'load_json_fn': load_json_file,
        'atomic_write_json_fn': atomic_write_json,
        'atomic_write_text_fn': atomic_write_text,
        'epg_meta_storage_path_fn': epg_meta_storage_path,
        'epg_xml_storage_path_fn': epg_xml_storage_path,
        'server_slot_storage_path_fn': server_slot_storage_path,
        'safe_delete_file_fn': storage_runtime.safe_delete_file,
        'ensure_profile_dir_fn': ensure_profile_dir,
        'ensure_parent_dir_fn': ensure_parent_dir,
        'safe_requests_get_fn': _safe_requests_get,
        'get_base_url_fn': lambda: base_url,
        'fingerprint_fn': lambda: _fingerprint(addon),
        'clear_desc_caches_fn': lambda: None,
        'current_server_scope_identity_fn': lambda: _server_slot_scope_name(addon),
        # Aqui o download é permitido porque service.py roda fora da navegação do usuário.
        'download_on_demand': True,
    }

    try:
        ensure_profile_dir()
        epg_runtime.epg_load_parsed(ctx, force=False)
        _log('EPG XMLTV preparado em background pelo service.py.', getattr(xbmc, 'LOGINFO', None))
        return True
    except Exception as exc:
        _log('Falha ao preparar EPG XMLTV no service.py: {}'.format(exc), getattr(xbmc, 'LOGWARNING', None))
        return False


def _wait_for_abort(monitor, seconds):
    try:
        if monitor is not None:
            return bool(monitor.waitForAbort(float(seconds)))
    except Exception:
        pass
    try:
        if xbmc is not None:
            xbmc.sleep(int(float(seconds) * 1000))
    except Exception:
        pass
    return False


def run_service_loop():
    monitor = None
    try:
        if xbmc is not None:
            monitor = xbmc.Monitor()
    except Exception:
        monitor = None

    # Pequena pausa abortável para não disputar I/O logo no boot do Kodi.
    if _wait_for_abort(monitor, 2.5):
        return

    cleanup_downloaded_apks_on_startup()

    if _wait_for_abort(monitor, 1.5):
        return

    try:
        prepare_epg_xmltv_cache_on_startup(monitor=monitor)
    except Exception as exc:
        _log('Service EPG inicial finalizou com aviso: {}'.format(exc), getattr(xbmc, 'LOGDEBUG', None))

    # Mantém o service vivo de forma leve. Não baixa canal por canal e não trava a UI:
    # apenas verifica se venceu, se está perto de vencer, se falta cobertura futura
    # ou se a UI deixou pedido de refresh pendente.
    while True:
        if _wait_for_abort(monitor, EPG_SERVICE_CHECK_INTERVAL_SECS):
            break
        try:
            prepare_epg_xmltv_cache_on_startup(monitor=monitor)
        except Exception as exc:
            _log('Service EPG periódico finalizou com aviso: {}'.format(exc), getattr(xbmc, 'LOGDEBUG', None))


if __name__ == '__main__':
    try:
        run_service_loop()
    except Exception as exc:
        _log('Service finalizou com aviso: {}'.format(exc), getattr(xbmc, 'LOGDEBUG', None))
