#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
main.py – router principale di plugin.video.torproxy

Il router gestisce:
  - Menu principale (delega a DaddyHD resolver)
  - Proxy mode per addon esterni
  - Tor Network (onion_*)
  - Dispatcher per tutti i resolver siti (DaddyHD, ICN, TPB, ecc.)

Tutto il codice specifico di DaddyHD (canali, schedule, play, resolve_link)
è stato spostato in resources/resolvers/daddyhd.py
"""

import re
from typing import Optional

import variables as var
import functions as func
from models import Item
import xbmc


# ══════════════════════════════════════════════════════════════════════
# MENU PRINCIPALE
# ══════════════════════════════════════════════════════════════════════

def main_menu() -> None:
    """
    Punto di ingresso dell'addon — carica hometor.xml dal server.
    Il menu completo (DaddyLive, ICN, TPB, ecc.) è definito lato server
    in hometor.xml; l'addon non ha più voci hardcoded.

    Per aggiungere DaddyLive nell'XML usare:
        <plugin>
          <n>DaddyLive</n>
          <link>plugin://plugin.video.torproxy/?mode=daddy_menu</link>
          <thumbnail>https://...icona.png</thumbnail>
        </plugin>
    """
    onion_menu()


# ══════════════════════════════════════════════════════════════════════
# PROXY MODE  (chiamato da addon esterni)
# ══════════════════════════════════════════════════════════════════════

def _is_internal_dlstreams_path(url: str) -> bool:
    if not url:
        return False
    if url.startswith('/') and url.endswith('.php') and 'stream-' in url:
        return True
    if 'dlstreams.top' in url and url.endswith('.php') and 'stream-' in url:
        return True
    return False


def _build_req_headers(extra_headers: str) -> dict:
    req_headers = dict(var.HEADERS)
    if not extra_headers:
        return req_headers
    for part in extra_headers.split('&'):
        if '=' in part:
            k, v = part.split('=', 1)
            req_headers[k.strip()] = v.strip()
    return req_headers


def play_via_tor(url: str, title: str = '', icon: str = '',
                 extra_headers: str = '') -> None:
    if not url:
        func.log('[torproxy] play_via_tor: nessun URL fornito')
        var.system_exit()
        return

    func.log(f'[torproxy] play_via_tor ricevuto: {url}')

    if '|' in url:
        parts = url.split('|', 1)
        url   = parts[0].strip()
        if not extra_headers:
            extra_headers = parts[1].strip()
            if extra_headers.startswith('!'):
                extra_headers = extra_headers[1:]
        func.log(f'[torproxy] headers estratti da pipe: {extra_headers}')

    req_headers = _build_req_headers(extra_headers)

    if url.endswith('.m3u8') or '.m3u8?' in url:
        func.log(f'[torproxy] URL m3u8 diretto → proxy locale: {url}')
        final_url = _serve_via_local_proxy(url, req_headers)
        if not final_url:
            final_url = url

    elif _is_internal_dlstreams_path(url):
        func.log(f'[torproxy] path interno dlstreams → daddyhd.resolve_link: {url}')
        try:
            from resources.resolvers.daddyhd import resolve_link
            final_url = resolve_link(url)
        except Exception as exc:
            func.log(f'[torproxy] resolve_link fallito: {exc}')
            final_url = url

    else:
        func.log(f'[torproxy] URL esterno Xtream → fetch via Tor + proxy locale: {url}')
        final_url = _serve_xtream_via_local_proxy(url, req_headers)
        if not final_url:
            func.log('[torproxy] proxy locale fallito, impossibile riprodurre')
            func.ok_dialog('Impossibile riprodurre il flusso via Tor.')
            var.system_exit()
            return

    if not final_url:
        func.ok_dialog('Impossibile risolvere il flusso via Tor.')
        var.system_exit()
        return

    func.log(f'[torproxy] URL finale per Kodi: {final_url}')

    display_title = title or 'Tor Stream'
    list_item     = var.list_item(display_title, path=final_url)
    func.set_info(list_item, {'title': display_title})
    if icon:
        list_item.setArt({'thumb': icon, 'icon': icon})
    list_item.setProperty('inputstream', 'inputstream.ffmpegdirect')
    list_item.setMimeType('application/x-mpegURL')
    list_item.setProperty('inputstream.ffmpegdirect.is_realtime_stream', 'true')
    list_item.setProperty('inputstream.ffmpegdirect.manifest_type', 'hls')

    var.set_resolved_url(var.handle, True, listitem=list_item)
    func.log(f'[torproxy] Riproduzione avviata: {final_url}')


def _serve_xtream_via_local_proxy(url: str, req_headers: dict) -> Optional[str]:
    try:
        from tor_manager import get_tor_manager
        from local_proxy import serve_manifest

        tor  = get_tor_manager()
        resp = tor.session.get(url, headers=req_headers, timeout=20,
                               allow_redirects=True, stream=False)
        final_url = resp.url
        content   = resp.text

        func.log(f'[torproxy] Tor GET status={resp.status_code}, url finale={final_url}')

        if '#EXTM3U' in content:
            from urllib.parse import urlparse as _up
            referer   = f'{_up(final_url).scheme}://{_up(final_url).netloc}'
            local_url = serve_manifest(fetch_url=final_url, referer=referer,
                                       initial_content=content, get_func=func.get)
            func.log(f'[torproxy] proxy locale avviato: {local_url}')
            return local_url

        return _serve_via_local_proxy(final_url, req_headers)

    except Exception as exc:
        func.log(f'[torproxy] _serve_xtream_via_local_proxy fallito: {exc}')
        return None


def _serve_via_local_proxy(url: str, req_headers: dict) -> Optional[str]:
    try:
        from tor_manager import get_tor_manager
        from local_proxy import serve_manifest
        from urllib.parse import urlparse as _up

        tor       = get_tor_manager()
        resp      = tor.session.get(url, headers=req_headers, timeout=20, allow_redirects=True)
        content   = resp.text
        final_url = resp.url

        if '#EXTM3U' not in content:
            func.log(f'[torproxy] _serve_via_local_proxy: risposta non m3u8 per {url}')
            return None

        referer   = f'{_up(final_url).scheme}://{_up(final_url).netloc}'
        local_url = serve_manifest(fetch_url=final_url, referer=referer,
                                   initial_content=content, get_func=func.get)
        return local_url

    except Exception as exc:
        func.log(f'[torproxy] _serve_via_local_proxy fallito: {exc}')
        return None


# ══════════════════════════════════════════════════════════════════════
# TOR NETWORK — menu da XML remoto hometor.xml
# ══════════════════════════════════════════════════════════════════════

def _clean_title(t: str) -> str:
    return re.sub(r'\[/?[A-Za-z0-9# ]+\]', '', t).strip()


def onion_menu(xml_url: str = '') -> None:
    from tor_manager import _port_open, TOR_HOST, TOR_SOCKS_PORT

    if not _port_open(TOR_HOST, TOR_SOCKS_PORT, timeout=2):
        var.notify_dialog(
            'Tor Network',
            'Tunnel Tor non attivo. Avvia TorThegroove o abilita Tor.',
            icon=var.addon_icon,
        )

    sites = func.load_onion_submenu(xml_url) if xml_url else func.load_onion_menu()

    if not sites:
        func.ok_dialog('Menu Tor Network non disponibile.\nControlla la connessione e riprova.')
        return

    func.ONION_SITES[:] = [s for s in sites if s.get('type') != 'doregex']

    for site in sites:
        title    = site.get('title', '')
        site_url = site.get('url', '').strip()
        thumb    = site.get('thumb') or var.addon_icon
        fanart   = site.get('fanart', '') or var.addon_fanart
        itype    = site.get('type', 'browse').lower()
        clean    = _clean_title(title)

        if not title:
            continue

        if itype == 'separator' or site_url.lower() == 'ignora':
            func.create_listitem(Item(
                title=title, type='dir', mode='onion_menu',
                link='', thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype == 'doregex':
            rname     = site.get('regex_name', '')
            raw_block = site.get('_raw_block', '')
            func.create_listitem(Item(
                title=title, type='dir', mode='onion_doregex',
                link=rname, title2=raw_block,
                thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype == 'custom' or site_url == 'CUSTOM':
            func.create_listitem(Item(
                title=title or '[COLOR deepskyblue]🧅  Apri URL .onion[/COLOR]',
                type='dir', mode='onion_input',
                thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype == 'submenu':
            ref = site.get('submenu') or site_url
            func.create_listitem(Item(
                title=title, type='dir', mode='onion_submenu',
                link=ref, thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype in ('play', 'stream', 'video', 'audio'):
            func.create_listitem(Item(
                title=title, type='file', mode='onion_play',
                link=site_url, thumbnail=thumb, fanart=fanart, is_media=True,
            ))
            continue

        if itype == 'regex':
            rx  = site.get('regex', '')
            grp = site.get('regex_group', '1')
            func.create_listitem(Item(
                title=title, type='dir', mode='onion_regex',
                link=site_url, title2=f'{rx}|||{grp}',
                thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype == 'search':
            sp = site.get('search_param', 'q')
            sx = site.get('search_path', '/search')
            func.create_listitem(Item(
                title=title, type='dir', mode='onion_search',
                link=site_url, title2=f'{sp}|||{sx}',
                thumbnail=thumb, fanart=fanart, is_media=False,
            ))
            continue

        if itype == 'plugin' or site_url.startswith('plugin://'):
            import xbmcplugin as _xbp
            liz = var.list_item(title)
            liz.setArt({'thumb': thumb, 'icon': thumb, 'fanart': fanart})
            _xbp.addDirectoryItem(var.handle, site_url, liz, True)
            continue

        sp = site.get('search_param', 'q')
        sx = site.get('search_path', '/search')
        func.create_listitem(Item(
            title=title, type='dir', mode='onion_browse',
            link=site_url, thumbnail=thumb, fanart=fanart, is_media=False,
        ))
        if sp and sx:
            func.create_listitem(Item(
                title=f'[COLOR yellow]  🔍  Cerca in: {clean}[/COLOR]',
                type='dir', mode='onion_search',
                link=site_url, title2=f'{sp}|||{sx}',
                thumbnail=thumb, fanart=fanart, is_media=False,
            ))


def onion_input() -> None:
    kb = xbmc.Keyboard('http://', 'Inserisci URL .onion', False)
    kb.doModal()
    if not kb.isConfirmed():
        return
    url = kb.getText().strip()
    if not url:
        return
    if not url.startswith('http'):
        url = 'http://' + url
    onion_browse(url, 'Tor: ' + url[:40])


def onion_browse(url: str, title: str = '') -> None:
    func.log(f'[torproxy] onion_browse: {url}')
    var.notify_dialog('Tor Network', f'Connessione a {url[:50]}…', icon=var.addon_icon)

    html = func.get_onion(url, timeout=30)
    if not html:
        func.ok_dialog(
            f'Impossibile raggiungere:\n{url}\n\n'
            'Verifica che Tor sia attivo su 127.0.0.1:9050'
        )
        return

    func.create_listitem(Item(
        title='[COLOR deepskyblue][B]🔎  Cerca contenuti nel sito[/B][/COLOR]',
        type='dir', mode='onion_search',
        link=url, thumbnail=var.addon_icon, is_media=False,
    ))

    links = func.parse_onion_links(html, url)
    if not links:
        func.ok_dialog('Pagina raggiunta ma nessun link trovato.')
        return

    _order = {'stream': 0, 'video': 1, 'audio': 2, 'magnet': 3,
               'torrent': 4, 'image': 5, 'onion': 6, 'http': 7}
    links.sort(key=lambda x: _order.get(x.get('type', 'http'), 9))

    for entry in links:
        func.create_onion_listitem(entry, mode='onion_browse')


def onion_search(site_url: str, title: str = '', title2: str = '') -> None:
    clean = _clean_title(title) or site_url[:40]
    kb    = xbmc.Keyboard('', f'Cerca in: {clean}', False)
    kb.doModal()
    if not kb.isConfirmed():
        return
    query = kb.getText().strip()
    if not query:
        return

    search_param = 'q'
    search_path  = '/search'
    if title2 and '|||' in title2:
        parts        = title2.split('|||', 1)
        search_param = parts[0].strip() or 'q'
        search_path  = parts[1].strip() or '/search'

    var.notify_dialog('Tor Network', f'Ricerca: {query}…', icon=var.addon_icon)
    results = func.onion_search(site_url, query,
                                 search_param=search_param,
                                 search_path=search_path)
    if not results:
        func.ok_dialog(f'Nessun risultato per:\n"{query}"\n\n'
                       'Il sito potrebbe non supportare la ricerca via URL.')
        return

    _order = {'stream': 0, 'video': 1, 'audio': 2, 'magnet': 3,
               'torrent': 4, 'image': 5, 'onion': 6, 'http': 7}
    results.sort(key=lambda x: _order.get(x.get('type', 'http'), 9))

    for entry in results:
        func.create_onion_listitem(entry, mode='onion_browse')


def onion_search_custom() -> None:
    kb_url = xbmc.Keyboard('http://', 'URL sito .onion da cercare', False)
    kb_url.doModal()
    if not kb_url.isConfirmed():
        return
    site_url = kb_url.getText().strip()
    if not site_url:
        return
    if not site_url.startswith('http'):
        site_url = 'http://' + site_url
    onion_search(site_url, title=site_url)


def onion_submenu(xml_ref: str, title: str = '') -> None:
    func.log(f'[torproxy] onion_submenu: {xml_ref}')
    var.notify_dialog('Tor Network', 'Carico sottomenu…', icon=var.addon_icon)
    onion_menu(xml_url=xml_ref)


def onion_regex(page_url: str, regex_spec: str, title: str = '') -> None:
    from onion_xml_builder import build_listitems

    pattern = regex_spec
    group   = 1
    if '|||' in regex_spec:
        parts   = regex_spec.split('|||', 1)
        pattern = parts[0]
        try:
            group = int(parts[1])
        except ValueError:
            group = 1

    var.notify_dialog('Tor Network', 'Estrazione contenuti…', icon=var.addon_icon)
    results = func.onion_regex_browse(page_url, pattern, group=group, base_url=page_url)
    if not results:
        func.ok_dialog('Nessun risultato trovato con la regex specificata.')
        return
    build_listitems(results)


def onion_torrent(url: str, title: str = '') -> None:
    import os
    import xbmcvfs

    func.log(f'[torproxy] onion_torrent: {url}')
    data_bytes = None
    try:
        import requests as _req
        resp = _req.get(url, proxies=func._ONION_PROXY, timeout=30, verify=False)
        if resp.status_code < 400:
            data_bytes = resp.content
    except Exception as e:
        func.log(f'[torproxy] onion_torrent fetch fallito: {e}')

    if not data_bytes:
        func.ok_dialog(f'Download .torrent fallito:\n{url}')
        return

    dl_path = xbmcvfs.translatePath('special://home/downloads/')
    os.makedirs(dl_path, exist_ok=True)
    fname = (title or url.split('/')[-1]).replace('/', '_') + '.torrent'
    fpath = os.path.join(dl_path, fname)
    try:
        with open(fpath, 'wb') as f:
            f.write(data_bytes)
        func.log(f'[torproxy] .torrent salvato: {fpath}')
        var.notify_dialog('Tor Network', f'Scaricato: {fname}', icon=var.addon_icon)
    except Exception as e:
        func.log(f'[torproxy] salvataggio .torrent fallito: {e}')
        func.ok_dialog(f'Impossibile salvare il file:\n{e}')


def onion_doregex(regex_name: str, raw_xml: str, title: str = '') -> None:
    from onion_parser import execute_regex
    from onion_xml_builder import build_listitems

    func.log(f'[torproxy] onion_doregex: blocco="{regex_name}" xml_len={len(raw_xml)}')
    var.notify_dialog('Tor Network', 'Carico contenuti…', icon=var.addon_icon)

    xml_source = raw_xml
    if not xml_source or len(xml_source) < 20:
        try:
            import requests as _req
            resp = _req.get(func._ONION_HOME_XML, timeout=12,
                            headers={'User-Agent': var.user_agent})
            resp.raise_for_status()
            xml_source = resp.text
        except Exception as e:
            func.log(f'[torproxy] onion_doregex: ri-fetch fallito: {e}')
            func.ok_dialog(f'Impossibile caricare il blocco "{regex_name}".')
            return

    items = execute_regex(xml_source, regex_name)
    if not items:
        func.ok_dialog(f'Nessun risultato per il blocco "{regex_name}".')
        return
    build_listitems(items)


# ══════════════════════════════════════════════════════════════════════
# ROUTER
# ══════════════════════════════════════════════════════════════════════

def router(params: dict) -> None:
    mode      = params.get('mode')
    title     = params.get('title', '')
    title2    = params.get('title2') or ''
    link      = params.get('link', '')
    thumbnail = params.get('thumbnail', '')
    icon      = params.get('icon', thumbnail)
    url       = params.get('url', '')
    is_search = params.get('is_search') in (True, 'True')

    # ── Proxy mode (chiamato da addon esterni) ────────────────────────
    if mode == 'proxy':
        hdrs = params.get('hdrs', '')
        play_via_tor(url=url, title=title, icon=icon, extra_headers=hdrs)
        return

    # ── Menu principale ───────────────────────────────────────────────
    if not mode:
        main_menu()

    # ── Tor Network ───────────────────────────────────────────────────
    elif mode == 'onion_menu':
        onion_menu()

    elif mode == 'onion_submenu':
        onion_submenu(link or url, title)

    elif mode == 'onion_browse':
        onion_browse(link or url, title)

    elif mode == 'onion_input':
        onion_input()

    elif mode == 'onion_search':
        onion_search(link or url, title, title2)
        var.set_content(var.handle, 'videos')
        var.end_directory(var.handle)
        return

    elif mode == 'onion_search_custom':
        onion_search_custom()
        var.set_content(var.handle, 'videos')
        var.end_directory(var.handle)
        return

    elif mode == 'onion_regex':
        onion_regex(link or url, title2 or '', title)

    elif mode == 'onion_elementum':
        import xbmcgui as _xbmcgui_el
        from onion_xml_builder import onion_elementum_play
        func.log(f'[torproxy] onion_elementum: {(link or url)[:80]}')
        var.notify_dialog('Elementum', 'Recupero magnet…', icon=icon or var.addon_icon)
        elementum_url = onion_elementum_play(link or url)
        if not elementum_url:
            func.ok_dialog('Magnet link non trovato nella pagina.')
            var.set_resolved_url(var.handle, False, _xbmcgui_el.ListItem())
            return
        liz = _xbmcgui_el.ListItem(path=elementum_url)
        liz.setProperty('IsPlayable', 'true')
        var.set_resolved_url(var.handle, True, listitem=liz)
        return

    elif mode == 'onion_doregex':
        onion_doregex(link or url, title2 or '', title)

    elif mode == 'onion_torrent':
        onion_torrent(link or url, title)

    elif mode == 'onion_play':
        stream = func.resolve_onion_video(link or url)
        if not stream:
            func.ok_dialog('Nessuno stream trovato nella pagina.')
            return
        if isinstance(stream, str) and stream.startswith('magnet:'):
            xbmc.executebuiltin(
                f'PlayMedia(plugin://plugin.video.elementum/play?uri={stream})'
            )
            return
        if '.m3u8' in stream.lower() or '.mpd' in stream.lower():
            # riusa play_via_tor per m3u8
            play_via_tor(stream, title or 'Tor Stream', icon or '')
        else:
            import xbmcgui as _xg
            list_item = var.list_item(title or 'Tor Stream', path=stream)
            func.set_info(list_item, {'title': title or 'Tor Stream'})
            if icon:
                list_item.setArt({'thumb': icon, 'icon': icon})
            var.set_resolved_url(var.handle, True, listitem=list_item)
        return

    elif mode == 'onion_image':
        xbmc.executebuiltin(f'ShowPicture({link or url})')
        return

    elif mode == 'resolver':
        # Dispatcher con title2 = mode_id esplicito
        from resources.resolvers.dispatcher import dispatch
        handled = dispatch(title2, link or url, title, icon)
        if handled:
            var.set_content(var.handle, 'videos')
            var.end_directory(var.handle)
        return

    else:
        # Fallback: qualsiasi mode non nativo → dispatcher
        # Gestisce daddy_*, icn_*, tpb_*, zeno_*, ecc.
        func.log(f'[torproxy] router: mode "{mode}" non nativo → provo dispatcher')
        from resources.resolvers.dispatcher import dispatch
        handled = dispatch(mode, link or url, title, icon)
        if handled:
            var.set_content(var.handle, 'videos')
            var.end_directory(var.handle)
        else:
            func.log(f'[torproxy] router: mode "{mode}" non gestito')
        return

    # ── Chiusura directory ────────────────────────────────────────────
    var.set_content(var.handle, 'videos')
    var.end_directory(var.handle)


if __name__ == '__main__':
    import ast
    import sys
    if len(sys.argv) > 1:
        router(ast.literal_eval(sys.argv[1]))
