#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
onion_parser.py  –  plugin.video.torproxy

Motore regex/pyFunction per file XML caricati via Tor.
Ispirato a item_parser.py di TheGroove360 ma adattato per:
  - fetch via SOCKS5 Tor (socks5h://127.0.0.1:9050)
  - siti .onion e clearnet bloccati
  - integrazione con le funzioni onion di functions.py

FORMATI XML SUPPORTATI (lo stesso di TheGroove):
  <item>
    <name>Titolo</name>
    <link>$doregex[nome_regex]</link>
    <thumbnail>https://...</thumbnail>
    <regex> <n>nome_regex</n>
      <listrepeat><![CDATA[
        <title>[nome_regex.param1]</title>
        <link>[nome_regex.param2]</link>
        <thumbnail>[nome_regex.param3]</thumbnail>
      ]]></listrepeat>
      <expres><![CDATA[
        (regex_pattern)|(gruppo_titolo)
      ]]></expres>
      <!-- oppure pyFunction: -->
      <expres><![CDATA[#$pyFunction
        def get_thegroove_xml_fn(page="", page_src=""):
            ...
            return [(title, url, thumb), ...]
      ]]></expres>
      <page>http://sito.onion/path</page>
    </regex>
  </item>

TIPI ITEM RICONOSCIUTI:
  browse   → navigazione pagina via onion_browse
  play     → riproduzione diretta via onion_play
  stream   → m3u8/mpd via onion_play
  video    → file video via onion_play
  audio    → file audio via onion_play
  torrent  → scarica .torrent via onion_torrent
  magnet   → link magnet aperto da Kodi
  image    → visualizzatore immagini
  submenu  → carica altro XML via onion_submenu
  search   → ricerca nel sito via onion_search
"""

import re
import logging
from typing import Optional, List, Dict, Tuple, Any
from urllib.parse import urljoin, urlparse

log = logging.getLogger('onion_parser')

# ── Proxy Tor per fetch pagine .onion ─────────────────────────────────
_TOR_PROXY = {
    'http' : 'socks5h://127.0.0.1:9050',
    'https': 'socks5h://127.0.0.1:9050',
}

# ── Estensioni per rilevamento automatico tipo ─────────────────────────
_STREAM_EXT = ('.m3u8', '.mpd', '.m3u')
_VIDEO_EXT  = ('.mp4', '.mkv', '.avi', '.webm', '.mov', '.ts', '.flv', '.m4v')
_AUDIO_EXT  = ('.mp3', '.ogg', '.flac', '.wav', '.aac', '.opus', '.m4a')
_IMAGE_EXT  = ('.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg')


# ======================================================================
# FETCH VIA TOR
# ======================================================================

def _fetch(url: str, timeout: int = 20) -> Optional[str]:
    """
    Scarica un URL via Tor (socks5h) per .onion,
    oppure direttamente per URL clearnet.
    """
    import urllib3
    urllib3.disable_warnings()
    import requests as _req

    if not url or url.lower() in ('', 'ignora'):
        return None

    try:
        if '.onion' in url:
            resp = _req.get(url, proxies=_TOR_PROXY, timeout=timeout, verify=False)
        else:
            resp = _req.get(url, timeout=timeout, verify=False,
                            headers={'User-Agent': 'Mozilla/5.0'})
        if resp.status_code < 400:
            return resp.text
        log.warning('_fetch HTTP %d: %s', resp.status_code, url[:80])
    except Exception as e:
        log.error('_fetch errore (%s): %s', url[:80], e)
    return None


# ======================================================================
# PARSER BLOCCHI XML
# ======================================================================

def _tag(xml: str, tag: str) -> str:
    """Estrae il contenuto del primo tag (robusto a CDATA e spazi)."""
    m = re.search(
        r'<' + re.escape(tag) + r'>\s*(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?\s*</' + re.escape(tag) + r'>',
        xml, re.DOTALL
    )
    return m.group(1).strip() if m else ''


def _find_regex_block(xml: str, regex_name: str) -> Optional[str]:
    """
    Trova il blocco <regex>...<n>nome</n>...</regex> nel testo XML.
    Compatibile con la serializzazione JSON di TheGroove:
    accetta sia newline reali che literal \\n dopo <regex>.
    """
    # Prova con \s+ (newline reale) — caso normale
    pattern = r'<regex>(\s+<n>' + re.escape(regex_name) + r'</n>.*?)</regex>'
    m = re.search(pattern, xml, re.DOTALL)
    if m:
        return m.group(1)

    # Prova senza spazio — dopo JSON round-trip i \n diventano letterali
    pattern2 = r'<regex>(\\?n?<n>' + re.escape(regex_name) + r'</n>.*?)</regex>'
    m2 = re.search(pattern2, xml, re.DOTALL)
    if m2:
        return m2.group(1)

    # Prova con spazio letterale — fix per serializzazione TheGroove
    pattern3 = r'<regex>\s*<n>' + re.escape(regex_name) + r'</n>(.*?)</regex>'
    m3 = re.search(pattern3, xml, re.DOTALL)
    if m3:
        return '<n>' + regex_name + '</n>' + m3.group(1)

    return None


def _parse_regex_block(block: str) -> Dict[str, str]:
    """
    Estrae listrepeat, expres, page da un blocco <regex>.
    """
    listrepeat = _tag(block, 'listrepeat') or ''
    expres     = _tag(block, 'expres')     or ''
    page       = _tag(block, 'page')       or ''

    # Rimuove CDATA wrapper residui
    for wrapper in ('<![CDATA[', ']]>'):
        listrepeat = listrepeat.replace(wrapper, '')
        expres     = expres.replace(wrapper, '')
        page       = page.replace(wrapper, '')

    return {
        'listrepeat': listrepeat.strip(),
        'expres'    : expres.strip(),
        'page'      : page.strip(),
    }


# ======================================================================
# MOTORE REGEX (modalità regex pattern)
# ======================================================================

def _run_regex_mode(expres: str, page_html: str) -> List[Tuple]:
    """
    Applica la regex `expres` all'HTML della pagina.
    Restituisce lista di tuple (gruppi catturati).
    """
    try:
        pattern = re.compile(expres, re.MULTILINE | re.DOTALL)
        results = pattern.findall(page_html)
        # findall restituisce stringhe se 1 gruppo, tuple se >1
        if results and isinstance(results[0], str):
            results = [(r,) for r in results]
        return results
    except re.error as e:
        log.error('_run_regex_mode: pattern non valido: %s', e)
        return []


# ======================================================================
# MOTORE PYTHON (modalità #$pyFunction)
# ======================================================================

def _run_py_mode(expres: str, page: str, page_src: str = '') -> Any:
    """
    Esegue il codice Python nella <expres> in modo sicuro.
    Il codice deve definire get_thegroove_xml_fn(page, page_src).
    Restituisce il risultato della funzione.
    """
    # Estrae il corpo della funzione (rimuove il marcatore #$pyFunction)
    fn_body = re.sub(r'^\s*#\s*\$pyFunction\s*', '', expres, flags=re.MULTILINE).strip()

    if not fn_body:
        return []

    # Costruisce la chiamata
    fn_body += f'\nxml_py_res = get_thegroove_xml_fn({repr(page)}, {repr(page_src)})'

    local_ns: Dict = {}
    try:
        exec(fn_body, _build_exec_globals(), local_ns)  # noqa: S102
        return local_ns.get('xml_py_res', [])
    except Exception as e:
        log.error('_run_py_mode exec errore: %s', e)
        return []


def _build_exec_globals() -> Dict:
    """
    Namespace globale per exec() delle pyFunction.
    Espone le stesse funzioni disponibili nelle pyFunction di TheGroove
    più le funzioni onion specifiche di torproxy.
    """
    import requests as _requests
    import json as _json
    import re as _re
    import sys as _sys
    import os as _os
    import xbmc as _xbmc
    import xbmcaddon as _xbmcaddon
    import xbmcgui as _xbmcgui

    # Importa funzioni da functions.py (già caricato)
    try:
        import functions as _func
        _get_onion = _func.get_onion
        _get       = _func.get
        _get_soup  = _func.get_soup
    except Exception:
        _get_onion = _fetch
        _get       = lambda url, **kw: type('R', (), {'text': _fetch(url) or ''})()
        _get_soup  = None

    return {
        # Moduli standard
        'requests'   : _requests,
        'json'       : _json,
        're'         : _re,
        'sys'        : _sys,
        'os'         : _os,
        # Kodi
        'xbmc'       : _xbmc,
        'xbmcaddon'  : _xbmcaddon,
        'xbmcgui'    : _xbmcgui,
        # HTTP via Tor
        'get_onion'  : _get_onion,
        'get'        : _get,
        'get_soup'   : _get_soup,
        'fetch_tor'  : _fetch,
        # Proxy Tor (per requests.get(..., proxies=TOR_PROXY))
        'TOR_PROXY'  : _TOR_PROXY,
        '__builtins__': __builtins__,
    }


# ======================================================================
# BUILDER ITEM DAL LISTREPEAT
# ======================================================================

def _build_items_from_results(
    results   : List,
    listrepeat: str,
    regex_name: str,
) -> List[Dict]:
    """
    Applica lo schema <listrepeat> a ogni risultato della regex/pyFunction.
    Produce lista di dict {title, url, thumb, type, ...}.
    """
    items = []

    for row in results:
        # Normalizza a lista di stringhe
        if isinstance(row, str):
            params = [row]
        elif isinstance(row, (list, tuple)):
            params = [str(p) for p in row]
        else:
            continue

        schema = listrepeat

        # Sostituisce [regex_name.param1] ... [regex_name.paramN]
        for i, val in enumerate(params, start=1):
            placeholder = f'[{regex_name}.param{i}]'
            schema = schema.replace(placeholder, val)

        # Parsa lo schema come mini-XML item
        item = _parse_schema_item(schema)
        if item:
            items.append(item)

    return items


def _parse_schema_item(schema: str) -> Optional[Dict]:
    """
    Parsa un blocco schema (risultato di listrepeat espanso).
    Estrae title/name, link/url, thumbnail/thumb, plot, type, ...
    """
    title = (_tag(schema, 'title') or _tag(schema, 'name') or '').strip()
    url   = (_tag(schema, 'link')  or _tag(schema, 'url')  or '').strip()
    thumb = (_tag(schema, 'thumbnail') or _tag(schema, 'thumb') or '').strip()
    fanart= _tag(schema, 'fanart').strip()
    plot  = _tag(schema, 'plot').strip()
    itype = _tag(schema, 'type').strip().lower() or _detect_url_type(url)

    if not title or not url or url.lower() == 'ignora':
        return None

    return {
        'title' : title,
        'url'   : url,
        'thumb' : thumb,
        'fanart': fanart,
        'plot'  : plot,
        'type'  : itype or 'http',
    }


def _detect_url_type(url: str) -> str:
    """Rileva il tipo di un URL dall'estensione."""
    if not url:
        return 'http'
    if url.startswith('magnet:'):
        return 'magnet'
    path = urlparse(url).path.lower()
    for e in _STREAM_EXT:
        if path.endswith(e):
            return 'stream'
    for e in _VIDEO_EXT:
        if path.endswith(e):
            return 'video'
    for e in _AUDIO_EXT:
        if path.endswith(e):
            return 'audio'
    for e in _IMAGE_EXT:
        if path.endswith(e):
            return 'image'
    if path.endswith('.torrent'):
        return 'torrent'
    if '.onion' in url:
        return 'onion'
    return 'http'


# ======================================================================
# ENTRY POINT PRINCIPALE
# ======================================================================

def execute_regex(
    xml_source : str,
    regex_name : str,
    page_url   : str = '',
    page_src   : str = '',
) -> List[Dict]:
    """
    Esegue un blocco <regex> identificato da `regex_name` nel testo XML.

    Parametri:
        xml_source  : testo XML completo (item raw o intero file)
        regex_name  : nome del blocco regex da eseguire
        page_url    : URL da fetchare (sovrascrive <page> se specificato)
        page_src    : HTML già fetchato (se disponibile, evita la GET)

    Restituisce lista di dict {title, url, thumb, type, ...}.
    """
    block = _find_regex_block(xml_source, regex_name)
    if not block:
        log.error('execute_regex: blocco "%s" non trovato', regex_name)
        return []

    parts = _parse_regex_block(block)

    page    = page_url or parts['page']
    expres  = parts['expres']
    listrep = parts['listrepeat']

    if not expres:
        log.warning('execute_regex: <expres> vuota per "%s"', regex_name)
        return []

    # ── Modalità pyFunction ──────────────────────────────────────────
    if '#$pyFunction' in expres:
        log.debug('execute_regex: modalità pyFunction per "%s"', regex_name)
        # La pyFunction gestisce da sé il fetch se necessario
        raw = _run_py_mode(expres, page, page_src)

        # raw può essere già lista di dict oppure lista di tuple
        if not raw:
            return []

        if isinstance(raw, list) and raw and isinstance(raw[0], dict):
            # Già formato dict — restituisci direttamente
            return [r for r in raw if r.get('title') and r.get('url')]

        if isinstance(raw, str):
            # Stringa: URL singolo
            return [{'title': raw, 'url': raw, 'type': _detect_url_type(raw), 'thumb': ''}]

        # Lista di tuple → applica listrepeat se disponibile
        if listrep:
            return _build_items_from_results(raw, listrep, regex_name)

        # Senza listrepeat: tenta interpretazione diretta
        items = []
        for row in raw:
            if isinstance(row, str):
                items.append({'title': row, 'url': row,
                              'type': _detect_url_type(row), 'thumb': ''})
            elif isinstance(row, (list, tuple)) and len(row) >= 2:
                t, u = str(row[0]), str(row[1])
                th   = str(row[2]) if len(row) > 2 else ''
                items.append({'title': t, 'url': u,
                              'type': _detect_url_type(u), 'thumb': th})
        return items

    # ── Modalità regex pattern ────────────────────────────────────────
    log.debug('execute_regex: modalità regex per "%s"', regex_name)

    # Fetch pagina
    if page_src:
        html = page_src
    elif page:
        log.debug('execute_regex: fetch pagina %s', page[:80])
        html = _fetch(page)
    else:
        log.warning('execute_regex: nessuna pagina da fetchare per "%s"', regex_name)
        return []

    if not html:
        log.warning('execute_regex: pagina vuota per "%s"', regex_name)
        return []

    results = _run_regex_mode(expres, html)
    log.info('execute_regex: "%s" → %d risultati', regex_name, len(results))

    if not results:
        return []

    if listrep:
        return _build_items_from_results(results, listrep, regex_name)

    # Senza listrepeat: ogni match è un item diretto
    items = []
    for row in results:
        params = [row] if isinstance(row, str) else list(row)
        url_v  = params[0] if params else ''
        title_v = params[1] if len(params) > 1 else url_v.split('/')[-1][:60]
        thumb_v = params[2] if len(params) > 2 else ''
        if url_v:
            items.append({
                'title': title_v,
                'url'  : url_v,
                'thumb': thumb_v,
                'type' : _detect_url_type(url_v),
            })
    return items


def parse_xml_with_regex(xml_source: str) -> List[Dict]:
    """
    Parsa un intero file XML ed esegue tutti i blocchi $doregex trovati.
    Restituisce la lista flat di tutti gli item prodotti.

    Utile per caricare file XML che contengono sia item statici
    che item dinamici (con $doregex).
    """
    from onion_xml_builder import items_to_dicts

    # Prima raccoglie item statici (senza $doregex)
    static  = _parse_static_items(xml_source)

    # Poi esegue i blocchi dinamici
    dynamic: List[Dict] = []
    doregex_links = re.findall(r'\$doregex\[([^\]]+)\]', xml_source)
    for rname in doregex_links:
        items = execute_regex(xml_source, rname)
        dynamic.extend(items)

    return static + dynamic


def _parse_static_items(xml_source: str) -> List[Dict]:
    """Estrae item statici (link normale, senza $doregex)."""
    items = []
    blocks = re.findall(
        r'<(?:item|plugin|dir)>(.*?)</(?:item|plugin|dir)>',
        xml_source, re.DOTALL
    )
    for block in blocks:
        clean  = re.sub(r'<regex>.*?</regex>', '', block, flags=re.DOTALL)
        title  = (_tag(clean, 'name') or _tag(clean, 'n') or _tag(clean, 'title')).strip()
        url    = (_tag(clean, 'link') or _tag(clean, 'url')).strip()
        thumb  = (_tag(clean, 'thumbnail') or _tag(clean, 'thumb')).strip()
        fanart = _tag(clean, 'fanart').strip()
        plot   = _tag(clean, 'plot').strip()
        itype  = _tag(clean, 'type').strip().lower()

        if not title or not url:
            continue
        if '$doregex' in url:
            continue  # gestiti da parse_xml_with_regex
        if url.lower() == 'ignora':
            continue

        if not itype:
            itype = 'plugin' if url.startswith('plugin://') else _detect_url_type(url)

        items.append({
            'title' : title,
            'url'   : url,
            'thumb' : thumb,
            'fanart': fanart,
            'plot'  : plot,
            'type'  : itype,
        })
    return items
