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

import os
import io
import json
import re
import time
import uuid
import threading

from resources.lib.common import (
    ensure_dir, make_session, log, DEFAULT_TIMEOUT, get_addon,
    get_setting_bool, get_setting_enum_value, log_debug, log_exception
)

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

SESSION = make_session()
API_TEMPLATE = 'https://opensubtitles.stremio.homes/{lang}/ai-translated=false%7Cfrom=all%7CCauto-adjustment=true'
# VOD não deve travar esperando lotes longos de legendas. Uma legenda válida já
# atende o player; a segunda candidata só entra se a primeira falhar.
SUBTITLE_METADATA_TIMEOUT = (2, 4)
SUBTITLE_DOWNLOAD_TIMEOUT = (2, 4)
SUBTITLE_MAX_CANDIDATES = 2
# A preparação antes do player usa orçamento curto: evita concorrência de rede
# no início do vídeo sem transformar uma legenda lenta em bloqueio indefinido.
SUBTITLE_PREPARE_BUDGET_SECS = 3.5
ASYNC_JOB_PREFIX = 'job_'
ASYNC_JOB_SUFFIX = '.json'
ASYNC_JOB_TTL_SECS = 120



def _resolve_addon(addon=None):
    resolved = get_addon(addon)
    if resolved is not None:
        return resolved
    return xbmcaddon.Addon()


def _subtitles_log_debug(message, addon=None):
    log_debug(message, addon=addon, component='Subtitles')


def _safe_async_job_id(value):
    try:
        candidate = str(value or '').strip().lower()
    except Exception:
        candidate = ''
    if re.match(r'^[a-f0-9]{16,64}$', candidate):
        return candidate
    return ''


def _async_job_path(job_id, addon=None):
    safe_id = _safe_async_job_id(job_id)
    if not safe_id:
        return ''
    return os.path.join(get_subtitle_dir(addon), '{}{}{}'.format(
        ASYNC_JOB_PREFIX, safe_id, ASYNC_JOB_SUFFIX
    ))


def _atomic_write_json(path, payload):
    if not path:
        return False
    temp_path = '{}.{}.tmp'.format(path, uuid.uuid4().hex)
    try:
        with io.open(temp_path, 'w', encoding='utf-8') as file_handle:
            json.dump(payload, file_handle, ensure_ascii=True, sort_keys=True, separators=(',', ':'))
        try:
            os.replace(temp_path, path)
        except AttributeError:
            if os.path.exists(path):
                os.remove(path)
            os.rename(temp_path, path)
        return True
    except Exception:
        try:
            if os.path.exists(temp_path):
                os.remove(temp_path)
        except Exception:
            pass
        return False


def create_async_subtitle_job(imdb_id, media_type, season=None, episode=None,
                              playback_session='', playback_item='', stream_kind='', addon=None):
    """Cria um trabalho local sem URL ou segredo para anexar legenda após o play.

    O job é um arquivo efêmero no perfil do addon porque ``RunScript`` roda em
    outro interpretador Kodi. Somente metadados públicos e referências loopback
    opacas são persistidos; URL, token, Cookie e Authorization nunca entram no
    job nem no caminho do plugin.
    """
    addon = _resolve_addon(addon)
    if not imdb_id or not media_type:
        return ''
    job_id = uuid.uuid4().hex
    payload = {
        'job_id': job_id,
        'created_at': int(time.time()),
        'imdb_id': str(imdb_id),
        'media_type': str(media_type),
        'season': '' if season in (None, '') else str(season),
        'episode': '' if episode in (None, '') else str(episode),
        'playback_session': str(playback_session or ''),
        'playback_item': str(playback_item or ''),
        'stream_kind': str(stream_kind or '').lower(),
    }
    path = _async_job_path(job_id, addon)
    if not _atomic_write_json(path, payload):
        _subtitles_log_debug('Não foi possível agendar legenda em segundo plano.', addon)
        return ''
    _subtitles_log_debug('Legenda agendada em segundo plano (job={}).'.format(job_id[:8]), addon)
    return job_id


def load_async_subtitle_job(job_id, addon=None):
    addon = _resolve_addon(addon)
    safe_id = _safe_async_job_id(job_id)
    path = _async_job_path(safe_id, addon)
    if not path or not os.path.isfile(path):
        return {}
    try:
        with io.open(path, 'r', encoding='utf-8') as file_handle:
            payload = json.load(file_handle)
        if not isinstance(payload, dict) or payload.get('job_id') != safe_id:
            return {}
        created_at = int(payload.get('created_at') or 0)
        if not created_at or (time.time() - created_at) > ASYNC_JOB_TTL_SECS:
            remove_async_subtitle_job(safe_id, addon)
            _subtitles_log_debug('Job de legenda expirado descartado (job={}).'.format(safe_id[:8]), addon)
            return {}
        return payload
    except Exception as exc:
        _subtitles_log_debug('Falha ao carregar job de legenda: {}'.format(exc.__class__.__name__), addon)
        return {}


def remove_async_subtitle_job(job_id, addon=None):
    path = _async_job_path(job_id, addon)
    if not path:
        return False
    try:
        if os.path.exists(path):
            os.remove(path)
        return True
    except Exception:
        return False


def _subtitle_destination_name(subtitle_lang, index, job_id=''):
    safe_id = _safe_async_job_id(job_id)
    prefix = 'sub'
    if safe_id:
        prefix = '{}{}'.format(ASYNC_JOB_PREFIX, safe_id[:16])
    return '{}_{}_{}.vtt'.format(prefix, subtitle_lang.replace('-', '_'), index)


def get_subtitle_dir(addon=None):
    addon = _resolve_addon(addon)
    profile_dir = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
    return ensure_dir(os.path.join(profile_dir, 'subtitles'))


def get_subtitle_language_code(addon=None):
    addon = _resolve_addon(addon)
    return get_setting_enum_value('idioma_legenda', ['pt-br', 'en', 'es'], 'pt-br', addon=addon)


def get_subtitle_language_candidates(addon=None):
    addon = _resolve_addon(addon)
    preferred = get_subtitle_language_code(addon)
    fallback = get_setting_enum_value('fallback_legenda', ['', 'pt-pt', 'en', 'es'], '', addon=addon)
    candidates = []
    for code in (preferred, fallback):
        if code and code not in candidates:
            candidates.append(code)
    return candidates or ['pt-br']


def clear_subtitles_cache(addon=None):
    addon = _resolve_addon(addon)
    subtitle_dir = get_subtitle_dir(addon)
    removed = 0
    try:
        if os.path.isdir(subtitle_dir):
            for filename in os.listdir(subtitle_dir):
                path = os.path.join(subtitle_dir, filename)
                try:
                    if os.path.isfile(path):
                        os.remove(path)
                        removed += 1
                except Exception as exc:
                    _subtitles_log_debug('Falha ao remover legenda {}: {}'.format(path, exc), addon)
    except Exception as exc:
        _subtitles_log_debug('Erro limpando cache de legendas: {}'.format(exc), addon)
    return removed


def build_subtitles_api_url(imdb_id, media_type, lang, season=None, episode=None):
    base = API_TEMPLATE.format(lang=lang)
    if media_type == 'series':
        return '{}/subtitles/series/{}:{}:{}.json'.format(base, imdb_id, season, episode)
    return '{}/subtitles/movie/{}.json'.format(base, imdb_id)


def fetch_subtitles_metadata(imdb_id, media_type, lang, season=None, episode=None, session=None, timeout=None):
    session = session or SESSION
    url = build_subtitles_api_url(imdb_id, media_type, lang, season=season, episode=episode)
    response = session.get(url, timeout=timeout or SUBTITLE_METADATA_TIMEOUT)
    response.raise_for_status()
    payload = response.json()
    subtitles = payload.get('subtitles') or []
    return subtitles


def _sanitize_webvtt_file(destination_path, addon=None):
    """Corrige somente a marcação ``<<`` inválida observada em VTT externo.

    Alguns provedores enviam um marcador duplicado que o parser WebVTT do Kodi
    registra como erro. A limpeza ocorre apenas no arquivo recém-baixado, não
    altera legendas do usuário nem bloqueia a reprodução se a normalização não
    puder ser feita.
    """
    try:
        with open(destination_path, 'rb') as file_handle:
            payload = file_handle.read()
        if b'<<' not in payload:
            return False
        cleaned = payload.replace(b'<<', b'<')
        if cleaned == payload:
            return False
        with open(destination_path, 'wb') as file_handle:
            file_handle.write(cleaned)
        _subtitles_log_debug('Legenda WebVTT normalizada antes do player.', addon)
        return True
    except Exception as exc:
        _subtitles_log_debug('Falha não crítica ao normalizar WebVTT: {}'.format(exc), addon)
        return False


def download_subtitle(url, destination_path, session=None, addon=None, timeout=None):
    session = session or SESSION
    response = session.get(url, timeout=timeout or SUBTITLE_DOWNLOAD_TIMEOUT)
    response.raise_for_status()
    with open(destination_path, 'wb') as file_handle:
        file_handle.write(response.content)
    _sanitize_webvtt_file(destination_path, addon=addon)
    return destination_path


def _budget_remaining_seconds(deadline):
    if deadline is None:
        return None
    try:
        return max(0.0, float(deadline) - time.monotonic())
    except Exception:
        return 0.0


def _prepare_budget_expired(deadline, cancel_event=None):
    try:
        if cancel_event is not None and cancel_event.is_set():
            return True
    except Exception:
        pass
    remaining = _budget_remaining_seconds(deadline)
    return remaining is not None and remaining <= 0.0


def _bounded_subtitle_prepare(imdb_id, media_type, season=None, episode=None, addon=None,
                               clear_before=True, job_id='', time_budget=None,
                               progress_callback=None):
    """Executa a preparação com um prazo global real para não segurar o player.

    Requests trata ``connect`` e ``read`` como etapas distintas; por isso, só
    reduzir os timeouts internos ainda permitia ultrapassar o orçamento total.
    Aqui a rota de play aguarda no máximo ``time_budget``. A sessão temporária
    é fechada no limite e a thread daemon não pode manter o Kodi ou o catálogo
    presos por uma origem de legendas lenta.
    """
    try:
        budget = max(0.25, float(time_budget))
    except Exception:
        budget = SUBTITLE_PREPARE_BUDGET_SECS
    deadline = time.monotonic() + budget
    done = threading.Event()
    cancelled = threading.Event()
    result = {'paths': []}
    # Não reutiliza a sessão global: fechar o transporte no limite não pode
    # cancelar uma futura legenda em segundo plano nem outro fluxo do addon.
    bounded_session = make_session(retries=0)

    def _worker():
        try:
            result['paths'] = _get_and_download_subtitles_core(
                imdb_id, media_type, season=season, episode=episode, addon=addon,
                clear_before=clear_before, job_id=job_id, session=bounded_session,
                deadline=deadline, cancel_event=cancelled, progress_callback=None,
            )
        except Exception as exc:
            _subtitles_log_debug('Worker limitado de legenda falhou: {}.'.format(exc.__class__.__name__), addon)
            result['paths'] = []
        finally:
            try:
                bounded_session.close()
            except Exception:
                pass
            done.set()

    try:
        if progress_callback:
            progress_callback('consultando', '', 0)
    except Exception:
        pass
    worker = threading.Thread(target=_worker, name='OnePlaySubtitlePrepare', daemon=True)
    worker.start()
    remaining = _budget_remaining_seconds(deadline)
    if remaining is None:
        remaining = budget
    if not done.wait(max(0.0, remaining)):
        cancelled.set()
        try:
            bounded_session.close()
        except Exception:
            pass
        _subtitles_log_debug('Orçamento global de preparação de legenda atingido; player liberado.', addon)
        try:
            if progress_callback:
                progress_callback('tempo_limite', '', 0)
        except Exception:
            pass
        return []
    if _prepare_budget_expired(deadline, cancelled):
        # Mesmo que a origem termine na borda do prazo, não anexa a legenda
        # atrasada a esta abertura. O vídeo segue sem carga adicional.
        _subtitles_log_debug('Preparação de legenda concluiu fora do orçamento; resultado descartado.', addon)
        return []
    return list(result.get('paths') or [])


def _get_and_download_subtitles_core(imdb_id, media_type, season=None, episode=None, addon=None,
                                      clear_before=True, job_id='', session=None, deadline=None,
                                      cancel_event=None, progress_callback=None):
    """Núcleo de busca de legenda, usado normal ou pela preparação limitada."""
    addon = _resolve_addon(addon)
    active_session = session or SESSION
    if not imdb_id or not media_type:
        _subtitles_log_debug('Busca de legenda ignorada por ausência de imdb_id ou media_type.', addon)
        return []

    if media_type == 'series' and (season in (None, '') or episode in (None, '')):
        _subtitles_log_debug('Busca de legenda para série ignorada por falta de season/episode.', addon)
        return []

    if not get_setting_bool('legendasauto', True, addon=addon):
        _subtitles_log_debug('Legendas automáticas desativadas pelo usuário.', addon)
        return []

    subtitle_dir = get_subtitle_dir(addon)
    if clear_before:
        clear_subtitles_cache(addon)

    def _remaining_timeout(default_timeout):
        if deadline is None:
            return default_timeout
        remaining = _budget_remaining_seconds(deadline)
        if remaining is None or remaining <= 0.10:
            return None
        # Connect e read compartilham o mesmo prazo global. O controle externo
        # ainda corta exatamente no deadline, mas estes limites reduzem trabalho
        # residual na thread que está sendo cancelada.
        connect = min(float(default_timeout[0]), max(0.12, remaining * 0.35))
        read = min(float(default_timeout[1]), max(0.15, remaining * 0.55))
        return (connect, read)

    def _progress(stage, language='', candidate=0):
        if not progress_callback:
            return
        try:
            progress_callback(stage, language, candidate)
        except Exception:
            pass

    def _expired():
        return _prepare_budget_expired(deadline, cancel_event)

    for subtitle_lang in get_subtitle_language_candidates(addon):
        if _expired():
            _subtitles_log_debug('Orçamento de preparação de legenda atingido antes da consulta.', addon)
            _progress('tempo_limite', subtitle_lang, 0)
            break
        metadata_timeout = _remaining_timeout(SUBTITLE_METADATA_TIMEOUT)
        if metadata_timeout is None:
            _subtitles_log_debug('Orçamento de preparação de legenda atingido antes da consulta.', addon)
            _progress('tempo_limite', subtitle_lang, 0)
            break
        _progress('consultando', subtitle_lang, 0)
        try:
            subtitles = fetch_subtitles_metadata(
                imdb_id, media_type, subtitle_lang,
                season=season, episode=episode, session=active_session,
                timeout=metadata_timeout,
            )
        except Exception as exc:
            if _expired():
                _subtitles_log_debug('Consulta de legenda excedeu o orçamento global.', addon)
                _progress('tempo_limite', subtitle_lang, 0)
                return []
            log_exception('Subtitles', 'Erro ao consultar legenda {}'.format(subtitle_lang), exc, addon=addon, level=xbmc.LOGWARNING)
            continue
        if _expired():
            _subtitles_log_debug('Consulta de legenda terminou fora do orçamento global.', addon)
            _progress('tempo_limite', subtitle_lang, 0)
            return []

        if not subtitles:
            _subtitles_log_debug('Nenhuma legenda encontrada para {} no idioma {}.'.format(imdb_id, subtitle_lang), addon)
            continue

        for index, subtitle in enumerate(subtitles[:SUBTITLE_MAX_CANDIDATES]):
            if _expired():
                _subtitles_log_debug('Orçamento de preparação de legenda atingido antes do download.', addon)
                _progress('tempo_limite', subtitle_lang, index + 1)
                return []
            download_timeout = _remaining_timeout(SUBTITLE_DOWNLOAD_TIMEOUT)
            if download_timeout is None:
                _subtitles_log_debug('Orçamento de preparação de legenda atingido antes do download.', addon)
                _progress('tempo_limite', subtitle_lang, index + 1)
                return []
            subtitle_url = subtitle.get('url')
            if not subtitle_url:
                continue
            _progress('baixando', subtitle_lang, index + 1)
            destination = os.path.join(subtitle_dir, _subtitle_destination_name(subtitle_lang, index, job_id=job_id))
            try:
                download_subtitle(subtitle_url, destination, session=active_session, addon=addon, timeout=download_timeout)
                if _expired():
                    try:
                        if os.path.exists(destination):
                            os.remove(destination)
                    except Exception:
                        pass
                    _subtitles_log_debug('Download de legenda terminou fora do orçamento; arquivo descartado.', addon)
                    _progress('tempo_limite', subtitle_lang, index + 1)
                    return []
                _subtitles_log_debug(
                    'Legenda pronta no idioma {} (candidata {}/{}).'.format(
                        subtitle_lang, index + 1, min(len(subtitles), SUBTITLE_MAX_CANDIDATES)
                    ),
                    addon
                )
                return [destination]
            except Exception as exc:
                if _expired():
                    _subtitles_log_debug('Download de legenda excedeu o orçamento global.', addon)
                    _progress('tempo_limite', subtitle_lang, index + 1)
                    return []
                log_exception('Subtitles', 'Falha ao baixar legenda', exc, addon=addon, level=xbmc.LOGWARNING)

    return []


def get_and_download_subtitles(imdb_id, media_type, season=None, episode=None, addon=None,
                                clear_before=True, job_id='', time_budget=None, progress_callback=None):
    """Baixa a primeira legenda válida.

    Com ``time_budget`` a preparação é limitada por relógio de parede: o player
    é liberado no prazo mesmo quando DNS, TLS, connect ou leitura remota atrasam.
    Sem orçamento, mantém a busca completa usada pelo worker em segundo plano.
    """
    if time_budget is not None:
        return _bounded_subtitle_prepare(
            imdb_id, media_type, season=season, episode=episode, addon=addon,
            clear_before=clear_before, job_id=job_id, time_budget=time_budget,
            progress_callback=progress_callback,
        )
    return _get_and_download_subtitles_core(
        imdb_id, media_type, season=season, episode=episode, addon=addon,
        clear_before=clear_before, job_id=job_id, session=SESSION,
        deadline=None, cancel_event=None, progress_callback=progress_callback,
    )

def get_stremio_subtitle(imdb_id, season=None, episode=None, lang='pt-br'):
    media_type = 'series' if season and episode else 'movie'
    try:
        subtitles = fetch_subtitles_metadata(imdb_id, media_type, lang, season=season, episode=episode, session=SESSION)
        if not subtitles:
            log('[STREMIO] No subtitles found for {}'.format(imdb_id))
            return None
        subtitle_url = subtitles[0].get('url')
        if not subtitle_url:
            return None
        subtitle_dir = get_subtitle_dir()
        extension = '.vtt'
        destination = os.path.join(subtitle_dir, '{}_{}{}'.format(imdb_id, lang, extension))
        return download_subtitle(subtitle_url, destination, session=SESSION)
    except Exception as exc:
        log_exception('Subtitles', 'Falha geral de legendas', exc, addon=addon, level=xbmc.LOGWARNING)
        return None
