import requests
import re
import os
import gzip

PANEL_URL = "http://192.168.1.112:1236/playlist.m3u"
BASIC_USER = "Tamox"
BASIC_PASS = "Tamoxel"
MAPPED_FOLDER = "/mnt/nas/Daten/M3u Mapped/"
DYN_M3U_PATH = "/mnt/nas/Daten/M3u Mapped/V4/Dyn/Dyn.m3u"
OUTPUT_FINAL = "/home/o11/iptv/o11Final.m3u"
OUTPUT_FINAL_INTERN = "/home/o11/iptv/o11Final_intern.m3u"
OUTPUT_MISSING = "/home/o11/iptv/missing_for_generator.m3u"
OUTPUT_OTT = "/home/o11/iptv/o11Final_ott.m3u"
EPG_URL = 'https://oi88.jfzerosixtwo.one/epg_merged.xml.gz'
EPG_URL_INTERN = 'http://192.168.1.112:8090/epg_merged.xml.gz'
DOMAIN = "http://92.179.65.244:45454"
DOMAIN_INTERN = "http://192.168.1.112:1236"

EXCLUDE_GROUPS = ["dazn vod", "dazn none", "none", "vod"]

# --- JOYN EPG-MAPPING (aus Joyn OTT-Listen tvg-name -> echte EPG-ID) ---
JOYN_AT_EPG = {
    "atv": "tkmat_206965288109",
    "atv2": "tkmat_206965288109",
    "kronehittv": "tkmat_206966312058",
    "kronetv": "tkmat_206966312058",
    "oneterra": "tkmat_206965288149",
    "orf1": "tkmat_206965288091",
    "orf2": "tkmat_206965288091",
    "orf3": "tkmat_206965288091",
    "orfsport": "tkmat_206966824154",
    "puls24": "tkmat_206965288109",
    "puls4": "tkmat_206965288109",
    "servustv": "tkmat_206965288109",
    "w24": "tkmat_206965288149",
}

JOYN_CH_EPG = {
    "3": "chsun_SV09010",
    "3sat": "tkmd2_262730280155",
    "4": "chsun_SV09056",
    "5": "chsun_SV09250",
    "6": "chsun_SV09251",
    "7": "chsun_SV06312",
    "ardpluslindenstrae": "zttde_ard_plus_lindenstrasse_de",
    "arte": "tkmd2_274352679972",
    "bbcnews": "tkmd2_516781096167",
    "bbcone": "chsun_SV09091",
    "bbctwo": "chsun_SV09003",
    "bluezoom": "chsun_SV07125",
    "channel4": "swcch_89",
    "cnninternational": "tkmd2_389265960223",
    "comedyampshows": "wpude_comedyshows",
    "comedycentral": "tkmd2_389265448045",
    "defatv": "wpude_defatv",
    "dmax": "tkmd2_389265960233",
    "euronews": "tkmat_206969896072",
    "eurosport1": "tkmd2_274351656125",
    "filmgold": "tkmd2_736340520423",
    "himmlischeskino": "zt1u1_himmlisches_kino_de",
    "justcooking": "tkmat_771194408193",
    "justfishing": "tkmat_771197480069",
    "kabeleins": "tkmd2_274350120289",
    "kabeleinsclassics": "tkmd2_389264424231",
    "kabeleinsdoku": "tkmd2_389263912238",
    "kika": "tkmd2_262281768126",
    "landlusttv": "tkmd2_738497064302",
    "m6suisse": "chsun_SV09014",
    "motorvision": "tkmd2_389265448047",
    "motorvisionclassic": "wpude_motorvisionclassics",
    "moviedome": "tkmd2_764249128121",
    "moviedomefamily": "zttde_moviedome_family_de",
    "mtv": "tkmd2_389265960221",
    "naruto": "tkmd2_754136616350",
    "nitro": "tkmd2_389263912217",
    "ntv": "tkmd2_274352168016",
    "oneterra": "tkmat_771197480067",
    "orf1": "tkmat_206965288091",
    "prosieben": "tkmd2_274352679970",
    "prosiebenfun": "tkmd2_274351656121",
    "prosiebenmaxx": "tkmd2_389264424238",
    "puls4": "tkmat_206965288121",
    "rtlup": "tkmd2_389264424253",
    "rtlzwei": "tkmd2_274352679973",
    "s1": "chsun_SV09201",
    "sat1": "tkmd2_262269992391",
    "sat1emotions": "tkmd2_274352679974",
    "sat1gold": "tkmd2_389263912235",
    "scooore": "tkmd2_738500136328",
    "sixx": "tkmd2_389263912226",
    "sport1": "tkmat_206967848012",
    "srf1": "wpude_srf1",
    "srf2": "wpude_srf2",
    "srfinfo": "tkmat_206966824029",
    "starsingefahrstarsindanger": "131268",
    "superrtl": "tkmd2_274352679977",
    "swiss1": "chsun_SV09252",
    "tele1": "chsun_SV09253",
    "telebarn": "chsun_SV09257",
    "telebasel": "chsun_SV09259",
    "telem1": "chsun_SV09258",
    "teletop": "chsun_SV09372",
    "telezentralschweiz": "chsun_SV09377",
    "telezuri": "chsun_SV09254",
    "tlc": "tkmd2_389265448055",
    "toggoplus": "tkmd2_389264424245",
    "topfilme": "zttde_top_filme_de",
    "topscifi": "tkmd2_754137640065",
    "topserien": "tkmd2_389265960213",
    "toptruecrime": "tkmd2_754137640066",
    "tv24": "chsun_SV09256",
    "tv25": "chsun_SV09255",
    "tvo": "100364",
    "vox": "tkmd2_274350120287",
    "w9": "chsun_SV09552",
    "wedobigstories": "zttde_wedo_big_docs_de",
    "wedomovies": "tkmd2_389264424249",
    "wedosports": "wpude_wedosports",
    "welt": "tkmd2_274351656124",
    "worldoffreesports": "tkmd2_736340520422",
    "xplore": "tkmat_771196456064",
    "yugioh": "wpude_yugioh",
    "zdf": "tkmd2_262264872014",
    "zdfneo": "tkmd2_262729768194",
}



GROUP_ABBREV = {
    "AmazonPrime": "AP",
    "AT_CH_DE-Mix": "MIX",
    "B&B_24/7": "BB",
    "Dazn": "DZ",
    "Dyn": "DY",
    "JoynSwiss": "JS",
    "Mag1_DE": "M1",
    "Mag2_DE": "M2",
    "Mag_AT": "MAT",
    "Mag_DE_X": "MX",
    "MagentaPL": "MPL",
    "MagTV AT FHD": "MTA",
    "MAXtv_HR": "MAX",
    "PlayNow_262": "PN1",
    "PlayNow_271": "PN2",
    "Polsat_PL_V4": "POL",
    "Rtl+_Now_Events": "RTL",
    "SkyWoW_x_DE": "SKY",
    "Sport_Eleven_PL": "S11",
    "SwissTV_CH": "STV",
    "TivuMedia_IT": "TIV",
    "vavoo": "VAV",
    "Vavoo_DE": "VDE",
    "Waipu_DE": "WAI",
    "xxx": "X1",
    "xxx2": "X2",
    "xxx3": "X3",
}

def add_abbrev_prefix(extinf_line):
    """Fuegt [KUERZEL] Prefix fuer OTT Player hinzu"""
    m_group = re.search(r'group-title="([^"]+)"', extinf_line)
    if ',' not in extinf_line:
        return extinf_line
    before, disp = extinf_line.rsplit(',', 1)
    group = m_group.group(1) if m_group else ""
    abbrev = GROUP_ABBREV.get(group, group[:3].upper())
    return before + ",[" + abbrev + "] " + disp.strip()

def normalize(name):
    name = name.lower()
    name = re.sub(r'[^a-z0-9 ]', ' ', name)
    name = re.sub(r'\b(de|hd|fhd|uhd|4k)\b', '', name)
    name = re.sub(r'\s+', ' ', name)
    return name.strip()
def strip_group_prefix(name):
    """Entfernt [GroupName] Prefix aus Display-Namen"""
    return re.sub(r'^\[.*?\]\s*', '', name).strip()




import unicodedata as _ud
def _norm_joyn(n):
    n = re.sub(r'^\[[^\]]+\]\s*', '', n)
    n = _ud.normalize("NFKD", n).encode("ascii","ignore").decode().lower()
    n = re.sub(r'\b(hd|uhd|fhd|sd|4k)\b', '', n)
    n = re.sub(r'[^a-z0-9]', '', n)
    return n

def apply_joyn_epg(extinf_line):
    """Korrigiert tvg-id fuer Joyn_AT/Joyn_CH auf echte EPG-ID (per Sendername)."""
    grp = re.search(r'group-title="([^"]*)"', extinf_line)
    if not grp:
        return extinf_line
    g = grp.group(1)
    if g not in ("Joyn_AT", "Joyn_CH"):
        return extinf_line
    if ',' not in extinf_line:
        return extinf_line
    disp = extinf_line.rsplit(',', 1)[1]
    key = _norm_joyn(disp)
    table = JOYN_AT_EPG if g == "Joyn_AT" else JOYN_CH_EPG
    eid = table.get(key)
    if eid:
        if re.search(r'tvg-id="[^"]*"', extinf_line):
            extinf_line = re.sub(r'tvg-id="[^"]*"', f'tvg-id="{eid}"', extinf_line)
        else:
            extinf_line = extinf_line.replace('#EXTINF:-1', f'#EXTINF:-1 tvg-id="{eid}"', 1)
    return extinf_line

def extract(line, key):
    match = re.search(f'{key}="(.*?)"', line)
    return match.group(1) if match else ""

def clean_tvg_name(name):
    name = name.replace('+', '').strip()
    name = re.sub(r'\s+', ' ', name)
    return name.strip()

def get_group(extinf_line):
    match = re.search(r'group-title="([^"]*)"', extinf_line)
    return match.group(1).lower() if match else ""

def is_excluded(group_title):
    return group_title.lower().strip() in EXCLUDE_GROUPS

def update_dyn_m3u(panel_lines):
    print("[INFO] Aktualisiere Dyn M3U...")
    dyn_channels = {}
    i = 0
    while i < len(panel_lines):
        line = panel_lines[i]
        if line.startswith("#EXTINF") and "group-title=\"Dyn\"" in line:
            name = line.split(",")[-1].strip()
            match = re.search(r'\[Dyn\] DYN (\d+)$', name)
            if match:
                num = int(match.group(1))
                if 1 <= num <= 50:
                    j = i + 1
                    while j < len(panel_lines) and not panel_lines[j].strip():
                        j += 1
                    if j < len(panel_lines) and not panel_lines[j].startswith("#"):
                        dyn_channels[num] = {
                            "tvg_id": extract(line, "tvg-id"),
                            "tvg_name": extract(line, "tvg-name"),
                            "logo": extract(line, "tvg-logo"),
                            "stream": panel_lines[j].strip()
                        }
        i += 1
    print(f"[INFO] {len(dyn_channels)} DYN Sender vom Panel gefunden")
    if not dyn_channels:
        print("[WARN] Keine DYN Sender gefunden!")
        return
    output = ['#EXTM3U x-tvg-url=""', '']
    for num in sorted(dyn_channels.keys()):
        ch = dyn_channels[num]
        output.append(f'#EXTINF:-1 group-title="Dyn" tvg-id="{ch["tvg_id"]}" tvg-name="{ch["tvg_name"]}" tvg-logo="{ch["logo"]}" provider="Dyn" provider-logo="" provider-type="iptv",[Dyn] DYN {num}')
        output.append(ch["stream"])
        output.append('')
    os.makedirs(os.path.dirname(DYN_M3U_PATH), exist_ok=True)
    with open(DYN_M3U_PATH, "w", encoding="utf-8") as f:
        f.write("\n".join(output))
    print(f"[INFO] Dyn M3U aktualisiert: {DYN_M3U_PATH}")

def load_mapped():
    mapping = {}
    skipped = 0
    for root, dirs, files in os.walk(MAPPED_FOLDER):
        for file in files:
            if not file.endswith(".m3u"):
                continue
            path = os.path.join(root, file)
            with open(path, "r", encoding="utf-8", errors="ignore") as f:
                lines = f.readlines()
            for i in range(len(lines)):
                if lines[i].startswith("#EXTINF"):
                    line = lines[i]
                    group = extract(line, "group-title")
                    if is_excluded(group):
                        skipped += 1
                        continue
                    name = line.split(",")[-1].strip()
                    key = normalize(name)
                    tvg_name = extract(line, "tvg-name")
                    if not tvg_name:
                        tvg_name = re.sub(r'^\[.*?\]\s*', '', name).strip()
                    tvg_name = clean_tvg_name(tvg_name)
                    mapping[key] = {
                        "tvg_id": extract(line, "tvg-id"),
                        "tvg_name": tvg_name,
                        "logo": extract(line, "tvg-logo"),
                        "group": group,
                        "name": name
                    }
    print(f"[INFO] {len(mapping)} gemappte Sender geladen ({skipped} VOD/None übersprungen)")
    return mapping

def load_panel():
    r = requests.get(PANEL_URL, auth=(BASIC_USER, BASIC_PASS), timeout=60)
    print(f"[INFO] Panel Status: {r.status_code}")
    try:
        content = gzip.decompress(r.content).decode("utf-8")
        print("[INFO] gzip dekomprimiert ✅")
    except:
        content = r.text
        print("[INFO] kein gzip, plain text")
    print(f"[INFO] Erste 200 Zeichen: {content[:200]}")
    return content.splitlines()

def main():
    panel = load_panel()
    update_dyn_m3u(panel)
    mapping = load_mapped()
    extern_pairs = []
    intern_pairs = []
    missing = ['#EXTM3U']
    seen_missing = set()
    mapped_count = 0
    unmapped_count = 0
    excluded_count = 0
    i = 0
    while i < len(panel):
        if panel[i].startswith("#EXTINF"):
            line = panel[i]
            j = i + 1
            while j < len(panel) and not panel[j].strip():
                j += 1
            if j >= len(panel) or panel[j].startswith("#"):
                i += 1
                continue
            stream = panel[j].strip()
            if is_excluded(get_group(line)):
                excluded_count += 1
                i = j + 1
                continue
            name = line.split(",")[-1].strip()
            key = normalize(name)
            stream_extern = stream.replace("http://192.168.1.112:1236", DOMAIN)
            stream_intern = stream.replace("http://192.168.1.112:1236", DOMAIN_INTERN)
            if key in mapping:
                data = mapping[key]
                if is_excluded(data["group"]):
                    excluded_count += 1
                    i = j + 1
                    continue
                new_line = f'#EXTINF:-1 tvg-id="{data["tvg_id"]}" tvg-name="{data["tvg_name"]}" tvg-logo="{data["logo"]}" group-title="{data["group"]}",{strip_group_prefix(data["name"])}'
                new_line = apply_joyn_epg(new_line)
                extern_pairs.append((new_line, stream_extern))
                intern_pairs.append((new_line, stream_intern))
                mapped_count += 1
            else:
                before, disp = line.strip().rsplit(",", 1)
                clean_line = before + "," + strip_group_prefix(disp)
                clean_line = apply_joyn_epg(clean_line)
                extern_pairs.append((clean_line, stream_extern))
                intern_pairs.append((clean_line, stream_intern))
                unmapped_count += 1
                if key not in seen_missing:
                    missing.append(line.strip())
                    missing.append(stream_intern)
                    seen_missing.add(key)
            i = j + 1
        else:
            i += 1
    extern_pairs.sort(key=lambda x: get_group(x[0]))
    intern_pairs.sort(key=lambda x: get_group(x[0]))
    output_extern = [f'#EXTM3U x-tvg-url="{EPG_URL}"']
    for extinf, stream in extern_pairs:
        output_extern.append(extinf)
        output_extern.append(stream)
    output_intern = [f'#EXTM3U x-tvg-url="{EPG_URL_INTERN}"']
    for extinf, stream in intern_pairs:
        output_intern.append(extinf)
        output_intern.append(stream)
    with open(OUTPUT_FINAL, "w", encoding="utf-8") as f:
        f.write("\n".join(output_extern))
    with open(OUTPUT_FINAL_INTERN, "w", encoding="utf-8") as f:
        f.write("\n".join(output_intern))

    # OTT Player Version mit Kuerzeln schreiben
    output_ott = [f'#EXTM3U x-tvg-url="{EPG_URL}"']
    for extinf, stream in extern_pairs:
        output_ott.append(add_abbrev_prefix(extinf))
        output_ott.append(stream)
    with open(OUTPUT_OTT, "w", encoding="utf-8") as f:
        f.write("\n".join(output_ott))
    print(f"[OUT] {OUTPUT_OTT}")
    with open(OUTPUT_MISSING, "w", encoding="utf-8") as f:
        f.write("\n".join(missing))
    print(f"[DONE] Gemappt: {mapped_count} | Nicht gemappt: {unmapped_count} | Excluded (VOD/None): {excluded_count}")
    print(f"[OUT] {OUTPUT_FINAL}")
    print(f"[OUT] {OUTPUT_FINAL_INTERN}")
    print(f"[OUT] {OUTPUT_MISSING}")

if __name__ == "__main__":
    main()
