#!/usr/bin/env python3

import hashlib
import random
import re
import time
import urllib.parse
import urllib.request
import urllib.error
from pathlib import Path

SOURCE = Path("/srv/iptv/channels.m3u")
OUTPUT = Path("/srv/iptv/channels-cached.m3u")
ARTWORK_DIR = Path("/pub/iptv/artwork")

BASE_URL = "http://localhost:8095/artwork"

ARTWORK_DIR.mkdir(parents=True, exist_ok=True)

logo_re = re.compile(r'tvg-logo="([^"]*)"')

def safe_filename(url):
    parsed = urllib.parse.urlparse(url)
    ext = Path(parsed.path).suffix.lower()

    if ext not in {".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg"}:
        ext = ".img"

    digest = hashlib.sha256(url.encode()).hexdigest()[:24]
    return digest + ext

def fetch(url, dest):

    if dest.exists() and dest.stat().st_size > 0:
        print(f"CACHE HIT: {dest.name}")
        return True

    headers = {
        "User-Agent": "Mozilla/5.0 (Kodi IPTV Logo Cache)",
        "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
    }

    for attempt in range(1, 5):
        try:
            print(f"DOWNLOAD attempt {attempt}/4")

            req = urllib.request.Request(url, headers=headers)

            with urllib.request.urlopen(req, timeout=20) as r:
                print(
                    f"HTTP {r.status} "
                    f"type={r.headers.get('Content-Type')} "
                    f"length={r.headers.get('Content-Length')}"
                )

                data = r.read()

            if not data:
                print("EMPTY RESPONSE")
                return False

            dest.write_bytes(data)

            print(f"WROTE {len(data):,} bytes")

            return True

        except urllib.error.HTTPError as e:

            if e.code == 429:
                wait = 15 * attempt

                print(
                    f"HTTP 429 RATE LIMITED "
                    f"-- waiting {wait}s"
                )

                time.sleep(wait)
                continue

            if e.code in {500, 502, 503, 504}:
                wait = 5 * attempt

                print(
                    f"HTTP {e.code} TEMP ERROR "
                    f"-- retrying in {wait}s"
                )

                time.sleep(wait)
                continue

            print(f"HTTP ERROR {e.code}: {e.reason}")
            return False

        except Exception as e:
            print(f"ERROR: {type(e).__name__}: {e}")
            return False

    print("GAVE UP after 4 attempts")
    return False

lines = SOURCE.read_text(
    encoding="utf-8",
    errors="replace"
).splitlines()

out = []

cached = 0
failed = 0
skipped_duplicate = 0

seen_urls = {}

for line in lines:
    if not line.startswith("#EXTINF"):
        out.append(line)
        continue

    m = logo_re.search(line)

    if not m:
        print("NO LOGO: entry has no tvg-logo")
        out.append(line)
        continue

    remote_url = m.group(1).strip()

    if not remote_url.startswith(("http://", "https://")):
        print(f"SKIP NON-HTTP: {remote_url}")
        out.append(line)
        continue

    print(f"\nPROCESSING: {remote_url}")

    if remote_url in seen_urls:
        local_url = seen_urls[remote_url]

        if local_url:
            print(f"DUPLICATE -> REUSE: {local_url}")

            line = line.replace(
                f'tvg-logo="{remote_url}"',
                f'tvg-logo="{local_url}"'
            )
        else:
            print("DUPLICATE -> previous attempt failed")

        skipped_duplicate += 1
        out.append(line)
        continue

    filename = safe_filename(remote_url)
    dest = ARTWORK_DIR / filename

    if dest.exists() and dest.stat().st_size > 0:
        print(f"ALREADY CACHED: {dest}")

    if fetch(remote_url, dest):
        local_url = f"{BASE_URL}/{filename}"
        seen_urls[remote_url] = local_url

        print(f"SUCCESS:")
        print(f"  file: {dest}")
        print(f"  url:  {local_url}")

        line = line.replace(
            f'tvg-logo="{remote_url}"',
            f'tvg-logo="{local_url}"'
        )

        cached += 1

    else:
        seen_urls[remote_url] = None
        failed += 1

        print("FAILED -> keeping original remote URL")

    out.append(line)

    print(
        f"STATUS: cached={cached} "
        f"duplicates={skipped_duplicate} "
        f"failed={failed}"
    )

    time.sleep(random.uniform(0.25, 0.75))

OUTPUT.write_text(
    "\n".join(out) + "\n",
    encoding="utf-8"
)

print()
print(f"Cached/reused:      {cached}")
print(f"Duplicate URLs:     {skipped_duplicate}")
print(f"Failed:             {failed}")
print(f"Output playlist:    {OUTPUT}")
