import requests
import json
import re
import time
from datetime import datetime, timezone, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed

# Hlavicka pre HTTP requesty
_HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36",
    "Accept-Language": "sk",
}

# Delay medzi requestami (sekundy), aby sme sa vyhli rate limitu
# Pri paralelnom fetchi nepotrebujeme delay medzi jednotlivymi requestami
_REQUEST_DELAY = 0

# Maximalny pocet paralelnych requestov na sweet.tv
# Vyssie cislo = rychlejsi fetch, ale viac 429 rate limitov
_MAX_WORKERS = 10

# Zoznam slugov pre vsetky SW kanaly (ziskane z redirectov na sweet.tv)
SW_SLUGS = {
    2005: "2005-family-fun-hd",
    2006: "2006-action-zone-hd",
    2007: "2007-komedialny-vesmir-hd",
    2008: "2008-real-drama-hd",
    2009: "2009-thriller-time-hd",
    2099: "2099-da-vinci-fun-learning-hd",
    2105: "2105-da-vinci-explore-the-world-hd",
    2114: "2114-da-vinci-operation-ouch-hd",
    2302: "2302-london-live-music-hd",
    2307: "2307-livepop-concerts-hd",
    2318: "2318-murder-maps-hd",
    2319: "2319-video-killed-the-radio-star-hd",
    2320: "2320-movie-stars-hd",
    2321: "2321-fashion-history-hd",
    2322: "2322-historical-figures-hd",
    2323: "2323-political-history-hd",
    2328: "2328-legends-of-the-opera-hd",
    2329: "2329-monarchies-dynasties-hd",
    2330: "2330-raiders-of-the-lost-art-hd",
    2331: "2331-presidents-leaders-hd",
    2337: "2337-myty-a-monstra-hd",
    2338: "2338-worlds-personalities-hd",
    2488: "2488-travel-adventures-hd",
    2648: "2648-men-with-the-pot-hd",
    2659: "2659-pure-nature-hd",
    2981: "2981-top-movies-hd",
    3009: "3009-truth-about-sports-hd",
    3098: "3098-unbeaten-channel-hd",
    3109: "3109-cocobi-tv-fun-play-hd",
    3110: "3110-cocobi-tv-song-hd",
    3128: "3128-sport-fishing-hd",
    3162: "3162-hollywood-stories-hd",
    3257: "3257-svet-naruby-hd",
    3315: "3315-saving-hope-hd",
    3319: "3319-cooking-with-byron-talbott-hd",
    3399: "3399-smurfs-hd",
    3443: "3443-talking-tom-shorts-hd",
    3453: "3453-blip-tv-hd",
    3474: "3474-family-entertainment-tv-hd",
    3479: "3479-horror-land-hd",
    3487: "3487-magical-smile-tv-hd",
    3517: "3517-mattel-junior-hd",
    3560: "3560-science-innovation-hd",
    3578: "3578-dobrodruzny-kaleidoskop-hd",
    3581: "3581-big-steps-hd",
    3583: "3583-svetove-hity-hd",
    3591: "3591-baby-shark-tv-hd",
    3599: "3599-labkova-patrola-hd",
    3605: "3605-filmove-legendy-hd",
    3606: "3606-mlade-ninja-korytnacky-hd",
    3621: "3621-podmorske-dobrodruzstva-hd",
    3633: "3633-kreslene-mesto-hd",
    3642: "3642-hry-o-peniaze-hd",
    3649: "3649-trace-sport-stars-hd",
    3684: "3684-trace-latina-hd",
    3687: "3687-trace-urban-hd",
    3712: "3712-trace-brasil-hd",
    3715: "3715-trace-uk-hd",
    3775: "3775-romantika-hd",
    3784: "3784-world-of-free-sports-hd",
    3787: "3787-megahit-hd",
    3790: "3790-thriller-plus-hd",
    3824: "3824-action-plus-hd",
    3825: "3825-premium-action-hd",
    3826: "3826-sci-fi-fantazia-hd",
    3828: "3828-premiere-hd",
    3829: "3829-premium-comedy-hd",
    3830: "3830-svet-zlocinu-hd",
    3831: "3831-premium-drama-hd",
    3833: "3833-premium-thriller-hd",
}

# Nase channel_id -> nazov kanala ako ho zobrazujeme
SW_NAMES = {
    2005: "Family Fun HD",
    2006: "Action Zone HD",
    2007: "Comedy Universe HD",
    2008: "Real Drama HD",
    2009: "Thriller Time HD",
    2099: "Da Vinci Fun Learning HD",
    2105: "Da Vinci Explore The World HD",
    2114: "Da Vinci Operation Ouch HD",
    2302: "London Live Music HD",
    2307: "Livepop Concerts HD",
    2318: "Murder Maps HD",
    2319: "Video Killed The Radio Star HD",
    2320: "Movie Stars HD",
    2321: "Fashion History HD",
    2322: "Historical Figures HD",
    2323: "Political History HD",
    2328: "Legends Of The Opera HD",
    2329: "Monarchies Dynasties HD",
    2330: "Raiders Of The Lost Art HD",
    2331: "Presidents And Leaders HD",
    2337: "Myty A Monstra HD",
    2338: "Worlds Personalities HD",
    2488: "Travel And Adventures HD",
    2648: "Men With The Pot HD",
    2659: "Pure Nature HD",
    2981: "Top Movies HD",
    3009: "Truth About Sports HD",
    3098: "Unbeaten Channel HD",
    3109: "Cocobi TV Fun Play HD",
    3110: "Cocobi TV Song HD",
    3128: "Sport Fishing HD",
    3162: "Hollywood Stories HD",
    3257: "The World Inside Out HD",
    3315: "Saving Hope HD",
    3319: "Cooking With Byron Talbott HD",
    3399: "Smurfs HD",
    3443: "Talking Tom Shorts HD",
    3453: "Blip TV HD",
    3474: "Family Entertainment TV HD",
    3479: "Horror Land HD",
    3487: "Magical Smile TV HD",
    3517: "Mattel Junior HD",
    3560: "Science And Innovation HD",
    3578: "Adventure Caleidoscope HD",
    3581: "Big Steps HD",
    3583: "Worldwide Hits HD",
    3591: "Baby Shark TV HD",
    3599: "PAW Patrol HD",
    3605: "Filmove Legendy HD",
    3606: "Teenage Mutant Ninja Turtles HD",
    3621: "SpongeBob SquarePants HD",
    3633: "Loud House HD",
    3642: "Money Games HD",
    3649: "Trace Sport Stars HD",
    3684: "Trace Latina HD",
    3687: "Trace Urban HD",
    3712: "Trace Brasil HD",
    3715: "Trace UK HD",
    3775: "Romance HD",
    3784: "World Of Free Sports HD",
    3787: "MEGAHIT HD",
    3790: "Thriller Plus HD",
    3824: "Action Plus HD",
    3825: "Premium ACTION HD",
    3826: "Sci-Fi And Fantasy HD",
    3828: "PREMIERE HD",
    3829: "Premium COMEDY HD",
    3830: "Crime TV HD",
    3831: "Premium DRAMA HD",
    3833: "Premium THRILLER HD",
}


def _get_nuxt_data(html):
    """Extrahuje Nuxt payload z HTML stranky."""
    m = re.search(r'<script[^>]*data-nuxt-data[^>]*>(.*?)</script>', html, re.DOTALL)
    if not m:
        m = re.search(r'<script id="__NUXT_DATA__"[^>]*>(.*?)</script>', html, re.DOTALL)
    if not m:
        return None
    return json.loads(m.group(1))


def _nuxt_get(data, idx):
    """Vrati hodnotu z Nuxt payload arrayu, nasleduje Ref/Reactive/ShallowReactive referencie."""
    if idx < 0 or idx >= len(data):
        return None
    val = data[idx]
    if isinstance(val, list) and len(val) == 2 and val[0] in ('Ref', 'Reactive', 'ShallowReactive', 'ShallowRef'):
        return _nuxt_get(data, val[1])
    return val


def _nuxt_resolve(data, val, depth=0):
    """Rekurzivne rozbali Nuxt payload strukturu (objekty, listy, referencie)."""
    if depth > 15:
        return val
    if isinstance(val, int):
        v = _nuxt_get(data, val)
        if v is None:
            return val
        if isinstance(v, list):
            # Ref/Reactive wrapper
            if len(v) == 2 and v[0] in ('Ref', 'Reactive', 'ShallowReactive', 'ShallowRef'):
                return _nuxt_resolve(data, v[1], depth + 1)
            # Nuxt objekt: prvy element su keys
            if v and isinstance(v[0], int):
                keys = _nuxt_get(data, v[0])
                if isinstance(keys, list):
                    result = {}
                    for ki, k in enumerate(keys):
                        if ki + 1 < len(v):
                            result[k] = _nuxt_resolve(data, v[ki + 1], depth + 1)
                        else:
                            result[k] = None
                    return result
            # Obycajny list
            return [_nuxt_resolve(data, item, depth + 1) for item in v]
        elif isinstance(v, dict):
            return {k: _nuxt_resolve(data, vv, depth + 1) if isinstance(vv, int) else vv for k, vv in v.items()}
        elif v is not None:
            return v
        return val
    if isinstance(val, list) and len(val) == 2 and val[0] in ('Ref', 'Reactive', 'ShallowReactive', 'ShallowRef'):
        return _nuxt_resolve(data, val[1], depth + 1)
    if isinstance(val, list):
        if val and isinstance(val[0], int):
            keys = _nuxt_get(data, val[0])
            if isinstance(keys, list):
                result = {}
                for ki, k in enumerate(keys):
                    if ki + 1 < len(val):
                        result[k] = _nuxt_resolve(data, val[ki + 1], depth + 1)
                    else:
                        result[k] = None
                return result
        return [_nuxt_resolve(data, item, depth + 1) for item in val]
    return val


def _iso_to_xmltv_time(iso_str):
    """Konvertuje ISO datetime '2026-07-03T20:16:00' na XMLTV format '20260703201600 +0000' v UTC."""
    try:
        dt = datetime.fromisoformat(iso_str)
        # Sweet.tv data su v local time (CET/CEST). Konvertujeme na UTC.
        # Pre april-september je CEST (UTC+2), inak CET (UTC+1)
        if 3 <= dt.month <= 10:
            if dt.month == 3 and dt.day < 25:
                pass  # Este moze byt CET
            elif dt.month == 10 and dt.day > 25:
                pass  # Uz moze byt CET
            else:
                dt = dt - timedelta(hours=2)
                return dt.strftime('%Y%m%d%H%M%S') + ' +0000'
        # CET
        dt = dt - timedelta(hours=1)
        return dt.strftime('%Y%m%d%H%M%S') + ' +0000'
    except Exception:
        return iso_str


def _escape_xml(text):
    """Escape XML special characters."""
    if not text:
        return ""
    return str(text).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace("'", "&apos;")


def fetch_channel_epg(channel_id, slug, retries=2):
    """
    Stiahne EPG pre jeden sweet.tv kanal.
    Vracia: list programov (dict s 'start', 'stop', 'title', 'epg_id') alebo None
    """
    url = f"https://sweet.tv/sk/tv_program/channel/{slug}"
    for attempt in range(retries + 1):
        try:
            r = requests.get(url, headers=_HEADERS, timeout=15, allow_redirects=True)
            if r.status_code == 429:
                time.sleep(3)
                continue
            if r.status_code != 200:
                return None
            break
        except Exception:
            if attempt < retries:
                time.sleep(2)
                continue
            return None
    else:
        return None

    # Parsujeme Nuxt data
    data_arr = _get_nuxt_data(r.text)
    if not data_arr:
        return None

    # Najdeme routu pre tuto channel EPG stranku
    try:
        root_data = _nuxt_get(data_arr, _nuxt_get(data_arr, 0)['data'])
    except (TypeError, IndexError, KeyError):
        return None
    if not isinstance(root_data, dict):
        return None

    route_key = None
    for k in root_data:
        if 'tv-program' in k and slug in k:
            route_key = k
            break
    if not route_key:
        return None

    page = _nuxt_resolve(data_arr, root_data[route_key])
    epg = _nuxt_resolve(data_arr, page.get('epgItems'))
    if not isinstance(epg, dict):
        return None

    # Ziskame zoznam programov zo vsetkych dni
    programs = []
    for date_key, day_progs in epg.items():
        if not isinstance(day_progs, list):
            continue
        for prog in day_progs:
            if not isinstance(prog, dict):
                continue
            start = prog.get('date_start', '')
            end = prog.get('date_end', '')
            title = prog.get('name', prog.get('title', ''))
            epg_id = prog.get('id', '')
            if start and end and title:
                programs.append({
                    'start': start,
                    'stop': end,
                    'title': title,
                    'epg_id': epg_id,
                })

    return programs


def fetch_all_epg(channel_ids=None, max_time=90):
    """
    Stiahne EPG pre vsetky (alebo vybrane) SW kanaly paralelne.
    
    Args:
        channel_ids: zoznam SW channel ID (int), napr. [2005, 3787]. None = vsetky.
        max_time: maximalny cas v sekundach pre vsetky requesty (None = bez limitu).
    
    Vracia: dict {channel_id: [programy]} alebo {channel_id: None} ak zlyhalo
    """
    ids = channel_ids or list(SW_SLUGS.keys())
    results = {cid: None for cid in ids}
    start_time = time.time()

    # Filtrujeme iba kanaly ktore maju slug
    valid = [(cid, SW_SLUGS[cid]) for cid in ids if SW_SLUGS.get(cid)]

    with ThreadPoolExecutor(max_workers=_MAX_WORKERS) as executor:
        futures = {executor.submit(fetch_channel_epg, cid, slug): cid for cid, slug in valid}

        for future in as_completed(futures):
            # Kontrola globalneho timeoutu
            if max_time and (time.time() - start_time) > max_time:
                # Zrusime ostatne futures (neblokujuce)
                for f in futures:
                    f.cancel()
                break

            cid = futures[future]
            try:
                results[cid] = future.result(timeout=20)
            except Exception:
                results[cid] = None

            time.sleep(_REQUEST_DELAY)

    return results


def generate_xmltv(results):
    """
    Generuje XMLTV string z vysledkov fetch_all_epg().
    
    Args:
        results: dict {channel_id: [programy] or None}
    
    Vracia: XMLTV string s <tv>...</tv>
    """
    channels_xml = []
    programmes_xml = []

    for cid in sorted(results.keys()):
        sw_key = f"SW{cid}"
        name = SW_NAMES.get(cid, f"SW{cid}")
        programs = results.get(cid)

        # Vzdy pridame channel tag (aj ked nema programy, pre mapovanie)
        channels_xml.append(f'  <channel id="{sw_key}">')
        channels_xml.append(f'    <display-name>{_escape_xml(name)}</display-name>')
        channels_xml.append(f'  </channel>')

        if not programs:
            continue

        for prog in programs:
            start_xmltv = _iso_to_xmltv_time(prog['start'])
            stop_xmltv = _iso_to_xmltv_time(prog['stop'])
            title = _escape_xml(prog['title'])

            programmes_xml.append(f'  <programme start="{start_xmltv}" stop="{stop_xmltv}" channel="{sw_key}">')
            programmes_xml.append(f'    <title>{title}</title>')
            programmes_xml.append(f'  </programme>')

    xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
    xml += '<tv generator-info-name="sweet.tv EPG">\n'
    xml += '\n'.join(channels_xml)
    if programmes_xml:
        xml += '\n' + '\n'.join(programmes_xml)
    xml += '\n</tv>\n'

    return xml
