#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
resources/resolvers/zeno_fm.py
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Resolver per Zeno.FM -- oltre 50.000 stazioni radio streaming.

Espone le funzioni chiamate dal dispatcher:
    show_home()              <- mode=zeno_home
    show_genres()            <- mode=zeno_genres
    show_countries()         <- mode=zeno_countries
    show_stations(url)       <- mode=zeno_stations   (lista stazioni da URL)
    show_search()            <- mode=zeno_search
    play_station(url)        <- mode=zeno_play       (estrae stream e riproduce)

COME FUNZIONA LO STREAM URL:
    Zeno.FM e' una app Next.js SSR. Ogni pagina stazione contiene un blocco
    <script id="__NEXT_DATA__"> con il JSON completo dei props SSR,
    incluso lo streamUrl della stazione.
    Formato stream: https://stream.zeno.fm/{stream_key}

URL DI NAVIGAZIONE CONFERMATI:
    Generi:   https://zeno.fm/radio/genres/
    Paesi:    https://zeno.fm/radio/countries/
    Lingue:   https://zeno.fm/radio/languages/
    Per genere:  https://zeno.fm/radio/genres/General/
    Per paese:   https://zeno.fm/radio/countries/Italy/
    Stazione:    https://zeno.fm/radio/{slug}/
"""

import re as _re
import json as _json
import sys
import os

_ADDON_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if _ADDON_ROOT not in sys.path:
    sys.path.insert(0, _ADDON_ROOT)

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

BASE_URL   = 'https://zeno.fm'
STREAM_BASE = 'https://stream.zeno.fm'

_UA = (
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
    'AppleWebKit/537.36 (KHTML, like Gecko) '
    'Chrome/124.0.0.0 Safari/537.36'
)
_HEADERS = {
    'User-Agent'     : _UA,
    'Accept'         : 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'it-IT,it;q=0.9,en;q=0.8',
    'Referer'        : BASE_URL + '/',
}


# ─────────────────────────────────────────────────────────────────────
# FETCH
# ─────────────────────────────────────────────────────────────────────

def _fetch(url: str) -> str:
    """GET con User-Agent moderno. Zeno.FM e' SSR, non ha Cloudflare."""
    try:
        import requests as _req
        r = _req.get(url, headers=_HEADERS, timeout=15, allow_redirects=True)
        n = len(r.text)
        func.log(f'[zeno] fetch HTTP {r.status_code}, {n} byte: {url[:60]}')
        return r.text if r.status_code == 200 else ''
    except Exception as e:
        func.log(f'[zeno] fetch errore ({url[:60]}): {e}')
        return ''


# ─────────────────────────────────────────────────────────────────────
# PARSER PAGINE DI ELENCO
# ─────────────────────────────────────────────────────────────────────

def _parse_station_links(html: str) -> list:
    """
    Estrae stazioni da una pagina lista di Zeno.FM.
    Ogni stazione e' un <a href="/radio/{slug}/"> con immagine e nome.

    Restituisce lista di dict: {title, url, thumb}
    """
    if not html:
        return []

    results = []
    seen    = set()

    # Strategia 1: BeautifulSoup
    try:
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, 'html.parser')
        pat  = _re.compile(r'^/radio/[^/]+/$')

        for a in soup.find_all('a', href=pat):
            href = a.get('href', '').strip()
            if not href or href in seen:
                continue
            seen.add(href)

            # Nome stazione: testo del link (pulito) o slug
            name = a.get_text(separator=' ', strip=True)
            # Rimuove "Sponsored" se presente
            name = _re.sub(r'^Sponsored\s*', '', name).strip()
            if not name:
                name = href.rstrip('/').split('/')[-1].replace('-', ' ').title()

            # Thumbnail: immagine zeno.fm nell'<a>
            img = a.find('img')
            thumb = ''
            if img:
                src = img.get('src', '')
                # Preferisce URL diretto al proxy zeno (non l'_ipx ottimizzato)
                if 'images.zeno.fm' in src or 'proxy.zeno.fm' in src:
                    thumb = src
                elif src.startswith('/'):
                    thumb = BASE_URL + src

            results.append({
                'title': name,
                'url'  : BASE_URL + href,
                'thumb': thumb,
            })

        func.log(f'[zeno] BS4 stazioni: {len(results)} trovate')

    except Exception as e:
        func.log(f'[zeno] BS4 errore: {e}')

    # Strategia 2: regex pura
    if not results:
        func.log('[zeno] fallback regex stazioni')
        for href, name in _re.findall(
            r'<a[^>]+href="(/radio/[^/"]+/)"[^>]*>\s*(?:<[^>]+>\s*)*([^<]{2,60})',
            html
        ):
            if href in seen:
                continue
            seen.add(href)
            name = name.strip()
            if name and 'Sponsored' not in name:
                results.append({'title': name, 'url': BASE_URL + href, 'thumb': ''})
        func.log(f'[zeno] regex stazioni: {len(results)} trovate')

    return results


def _parse_category_links(html: str, base_path: str) -> list:
    """
    Estrae categorie (generi/paesi/lingue) da una pagina indice.
    base_path es: '/radio/genres/', '/radio/countries/'
    """
    if not html:
        return []

    results = []
    seen    = set()

    try:
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, 'html.parser')
        pat  = _re.compile(r'^' + _re.escape(base_path) + r'.+')
        for a in soup.find_all('a', href=pat):
            href = a.get('href', '').strip()
            if not href or href in seen:
                continue
            seen.add(href)
            name = a.get_text(separator=' ', strip=True)
            if name:
                results.append({'title': name, 'url': BASE_URL + href})
    except Exception as e:
        func.log(f'[zeno] _parse_category_links BS4: {e}')
        # Fallback regex
        escaped = _re.escape(base_path)
        for href, name in _re.findall(
            rf'href="({escaped}[^"]+)"[^>]*>([^<]{{2,60}})', html
        ):
            if href in seen:
                continue
            seen.add(href)
            results.append({'title': name.strip(), 'url': BASE_URL + href})

    return results


# ─────────────────────────────────────────────────────────────────────
# ESTRAZIONE STREAM URL dalla pagina stazione
# ─────────────────────────────────────────────────────────────────────

def _get_stream_url(station_page_url: str) -> str:
    """
    Fetcha la pagina stazione ed estrae lo stream URL.

    Strategie in ordine:
      1. __NEXT_DATA__ JSON (Next.js SSR) -> props.pageProps.station.streamUrl
      2. Cerca stream.zeno.fm nel sorgente HTML
      3. Cerca pattern m3u/pls nella pagina
    """
    html = _fetch(station_page_url)
    if not html:
        return ''

    # ── Strategia 1: __NEXT_DATA__ ──────────────────────────────────
    m = _re.search(
        r'<script[^>]+id=["\']__NEXT_DATA__["\'][^>]*>\s*(\{.*?\})\s*</script>',
        html, _re.DOTALL
    )
    if m:
        try:
            data = _json.loads(m.group(1))
            # Naviga la struttura tipica: props.pageProps.station
            pp = data.get('props', {}).get('pageProps', {})

            # Prova vari percorsi nel JSON
            for path in [
                ['station', 'streamUrl'],
                ['station', 'stream', 'url'],
                ['station', 'stream'],
                ['initialState', 'station', 'streamUrl'],
                ['stationData', 'streamUrl'],
            ]:
                node = pp
                for key in path:
                    if isinstance(node, dict):
                        node = node.get(key, {})
                    else:
                        node = {}
                        break
                if isinstance(node, str) and node.startswith('http'):
                    func.log(f'[zeno] stream da __NEXT_DATA__ ({".".join(path)}): {node[:60]}')
                    return node

            # Cerca in tutta la struttura JSON un campo streamUrl
            raw = _json.dumps(data)
            sm = _re.search(r'"streamUrl"\s*:\s*"(https?://[^"]+)"', raw)
            if sm:
                func.log(f'[zeno] stream da __NEXT_DATA__ (search): {sm.group(1)[:60]}')
                return sm.group(1)

            # Cerca stream.zeno.fm nel JSON
            sm2 = _re.search(r'"(https://stream\.zeno\.fm/[^"]+)"', raw)
            if sm2:
                func.log(f'[zeno] stream da __NEXT_DATA__ (stream.zeno.fm): {sm2.group(1)[:60]}')
                return sm2.group(1)

        except Exception as e:
            func.log(f'[zeno] __NEXT_DATA__ parse errore: {e}')

    # ── Strategia 2: stream.zeno.fm nel sorgente ─────────────────────
    m2 = _re.search(r'(https://stream\.zeno\.fm/[a-zA-Z0-9]+)', html)
    if m2:
        func.log(f'[zeno] stream da sorgente: {m2.group(1)[:60]}')
        return m2.group(1)

    # ── Strategia 3: link audio diretto (.mp3/.aac/.m3u/.pls) ────────
    for pat in (
        r'(https?://[^\s"\'<>]+\.(?:mp3|aac|ogg|m3u8?)(?:\?[^\s"\'<>]*)?)',
        r'(https?://stream\.[^\s"\'<>]{5,80})',
    ):
        m3 = _re.search(pat, html, _re.I)
        if m3:
            func.log(f'[zeno] stream da pattern audio: {m3.group(1)[:60]}')
            return m3.group(1)

    func.log(f'[zeno] stream non trovato in: {station_page_url[:60]}')
    return ''


# ─────────────────────────────────────────────────────────────────────
# PUBBLICO
# ─────────────────────────────────────────────────────────────────────

def show_home() -> None:
    """Menu principale Zeno.FM con le sezioni di navigazione."""
    items = [
        ('[B][COLOR orange]Per Genere[/COLOR][/B]',   f'{BASE_URL}/radio/genres/',    ''),
        ('[B][COLOR deepskyblue]Per Paese[/COLOR][/B]', f'{BASE_URL}/radio/countries/', ''),
        ('[B][COLOR limegreen]Per Lingua[/COLOR][/B]', f'{BASE_URL}/radio/languages/', ''),
        ('[B][COLOR gold]Cerca stazione[/COLOR][/B]',  'SEARCH',                       ''),
        ('[B][COLOR white]Ultime aggiunte[/COLOR][/B]', f'{BASE_URL}/radio/',           ''),
    ]
    for title, url, thumb in items:
        if url == 'SEARCH':
            func.create_listitem(Item(
                title=title, type='dir', mode='zeno_search',
                thumbnail=thumb or var.addon_icon, is_media=False,
            ))
        else:
            func.create_listitem(Item(
                title=title, type='dir', mode='zeno_stations',
                link=url, thumbnail=thumb or var.addon_icon, is_media=False,
            ))


def show_genres() -> None:
    """Lista tutti i generi disponibili su Zeno.FM."""
    html = _fetch(f'{BASE_URL}/radio/genres/')
    cats = _parse_category_links(html, '/radio/genres/')
    if not cats:
        func.ok_dialog('Impossibile caricare i generi da Zeno.FM')
        return
    for cat in cats:
        func.create_listitem(Item(
            title=cat['title'],
            type='dir',
            mode='zeno_stations',
            link=cat['url'],
            thumbnail=var.addon_icon,
            is_media=False,
        ))


def show_countries() -> None:
    """Lista tutti i paesi disponibili su Zeno.FM."""
    html = _fetch(f'{BASE_URL}/radio/countries/')
    cats = _parse_category_links(html, '/radio/countries/')
    if not cats:
        func.ok_dialog('Impossibile caricare i paesi da Zeno.FM')
        return
    for cat in cats:
        func.create_listitem(Item(
            title=cat['title'],
            type='dir',
            mode='zeno_stations',
            link=cat['url'],
            thumbnail=var.addon_icon,
            is_media=False,
        ))


def show_stations(url: str, title: str = '') -> None:
    """
    Mostra la lista stazioni di una pagina Zeno.FM.
    Funziona per: generi, paesi, lingue, ricerca.
    """
    if not url:
        func.ok_dialog('URL non specificato')
        return

    func.log(f'[zeno] show_stations: {url}')
    var.notify_dialog('Zeno.FM', 'Carico stazioni...', icon=var.addon_icon)

    html = _fetch(url)
    if not html:
        func.ok_dialog(f'Impossibile raggiungere:\n{url}')
        return

    stations = _parse_station_links(html)
    if not stations:
        func.ok_dialog('Nessuna stazione trovata.')
        return

    func.log(f'[zeno] show_stations: {len(stations)} stazioni')
    for st in stations:
        func.create_listitem(Item(
            title=st['title'],
            type='file',
            mode='zeno_play',
            link=st['url'],
            thumbnail=st.get('thumb', '') or var.addon_icon,
            is_media=True,
        ))


def show_search() -> None:
    """Ricerca stazioni radio su Zeno.FM per nome."""
    import xbmc as _xbmc
    from urllib.parse import quote_plus

    kb = _xbmc.Keyboard('', 'Cerca stazione su Zeno.FM', False)
    kb.doModal()
    if not kb.isConfirmed():
        return
    query = kb.getText().strip()
    if not query:
        return

    var.notify_dialog('Zeno.FM', f'Cerca: {query}...', icon=var.addon_icon)
    q = quote_plus(query)

    # Zeno.FM usa ?search= nella pagina di esplorazione
    search_urls = [
        f'{BASE_URL}/radio?q={q}',
        f'{BASE_URL}/radio?search={q}',
        f'{BASE_URL}/search?q={q}',
        f'{BASE_URL}/explore?q={q}',
    ]

    html  = ''
    for su in search_urls:
        h = _fetch(su)
        if h and '/radio/' in h:
            stations = _parse_station_links(h)
            if stations:
                html = h
                func.log(f'[zeno] search OK da: {su}')
                break

    if not html:
        # Fallback: usa la pagina esplorazione e filtra per query
        h = _fetch(f'{BASE_URL}/radio/genres/General/')
        if h:
            all_stations = _parse_station_links(h)
            ql = query.lower().split()
            stations = [
                s for s in all_stations
                if all(w in s['title'].lower() for w in ql)
            ]
            if stations:
                func.log(f'[zeno] search fallback filtrato: {len(stations)} risultati')
                for st in stations:
                    func.create_listitem(Item(
                        title=st['title'], type='file', mode='zeno_play',
                        link=st['url'],
                        thumbnail=st.get('thumb', '') or var.addon_icon,
                        is_media=True,
                    ))
                return

        func.ok_dialog(
            f'Nessuna stazione trovata per: "{query}"\n'
            'Prova a cercare con parole diverse.'
        )
        return

    for st in _parse_station_links(html):
        func.create_listitem(Item(
            title=st['title'], type='file', mode='zeno_play',
            link=st['url'],
            thumbnail=st.get('thumb', '') or var.addon_icon,
            is_media=True,
        ))


def play_station(url: str, title: str = '') -> None:
    """
    Estrae lo stream URL dalla pagina stazione e avvia la riproduzione
    in Kodi come audio stream.
    """
    if not url:
        func.ok_dialog('URL stazione non specificato')
        return

    func.log(f'[zeno] play_station: {url}')
    var.notify_dialog('Zeno.FM', f'Connessione a {title or url[:40]}...', icon=var.addon_icon)

    stream_url = _get_stream_url(url)

    if not stream_url:
        func.ok_dialog(
            f'Impossibile trovare lo stream per:\n{url}\n\n'
            'La stazione potrebbe essere offline o richiedere login.'
        )
        return

    func.log(f'[zeno] riproduco stream: {stream_url}')

    display_title = title or url.rstrip('/').split('/')[-1].replace('-', ' ').title()
    list_item = var.list_item(display_title, path=stream_url)

    from functions import set_info
    set_info(list_item, {'title': display_title, 'mediatype': 'music'})
    list_item.setArt({'thumb': var.addon_icon, 'icon': var.addon_icon})

    # Audio stream — non usa inputstream.ffmpegdirect
    list_item.setMimeType('audio/mpeg')
    list_item.setProperty('IsPlayable', 'true')

    var.set_resolved_url(var.handle, True, listitem=list_item)
    func.log(f'[zeno] stream avviato: {stream_url}')
