import json
import re
import urllib.parse
import urllib.request
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed


SRC = Path("/var/www/html/Extract/wecima_movies_full.json")
OUT = Path("/var/www/html/Extract/playlist.m3u")
JSON_OUT = Path("/var/www/html/Extract/playlist.json")

EXTRACT = "http://127.0.0.1:9393/extract?url="

FASEL_API = "https://hrrejhp.com/scripttestfasel.php?api="

MAX_WORKERS = 16
TIMEOUT = 30
MIN_HEIGHT = 720
SAVE_EVERY = 10

UA = (
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    "AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/138.0.0.0 Safari/537.36"
)


def clean(value):
    return re.sub(r"\s+", " ", str(value or "")).strip()


def esc(value):
    return str(value or "").replace('"', "'")


def make_origin(referer):
    try:
        parsed = urllib.parse.urlparse(referer)

        if parsed.scheme and parsed.netloc:
            return f"{parsed.scheme}://{parsed.netloc}"

    except Exception:
        pass

    return ""


def is_m3u8(url):
    return ".m3u8" in clean(url).lower()


def is_mp4(url):
    return ".mp4" in clean(url).lower()


def is_fasel_source(video=None, link="", server=""):
    if isinstance(video, dict):
        link = clean(
            video.get("link")
            or video.get("url")
            or video.get("embed")
            or video.get("m3u8")
            or link
        )

        server = clean(
            video.get("server")
            or server
        )

    text = f"{server} {link}".lower()

    return any(
        value in text
        for value in (
            "fasel-hd.com",
            "fasel",
            "faselhd",
            "faisal",
            "faisl",
            "vip fast"
        )
    )


def fetch_text(
    url,
    timeout=10,
    referer="",
    useragent="",
    accept=""
):
    headers = {
        "User-Agent": useragent or UA,
        "Accept": accept or (
            "application/vnd.apple.mpegurl,"
            "application/x-mpegURL,"
            "application/json,"
            "text/plain,"
            "text/html,"
            "*/*"
        ),
        "Accept-Language": "en-US,en;q=0.9,ar;q=0.8",
        "Connection": "close"
    }

    if referer:
        headers["Referer"] = referer

        origin = make_origin(referer)

        if origin:
            headers["Origin"] = origin

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

    with urllib.request.urlopen(
        request,
        timeout=timeout
    ) as response:
        body = response.read(500000)

        return (
            response.status,
            body.decode("utf-8", "ignore"),
            response.geturl()
        )


def get_height(stream_info):
    match = re.search(
        r"RESOLUTION\s*=\s*\d+x(\d+)",
        stream_info,
        re.I
    )

    if not match:
        return 0

    try:
        return int(match.group(1))
    except Exception:
        return 0


def get_bandwidth(stream_info):
    match = re.search(
        r"(?:AVERAGE-)?BANDWIDTH\s*=\s*(\d+)",
        stream_info,
        re.I
    )

    if not match:
        return 0

    try:
        return int(match.group(1))
    except Exception:
        return 0


def resolve_best_quality(
    master_url,
    referer="",
    useragent=""
):
    master_url = clean(master_url)

    if not master_url:
        return ""

    if is_mp4(master_url):
        return ""

    if not is_m3u8(master_url):
        return ""

    try:
        status, text, final_url = fetch_text(
            master_url,
            timeout=15,
            referer=referer,
            useragent=useragent,
            accept=(
                "application/vnd.apple.mpegurl,"
                "application/x-mpegURL,"
                "application/mpegurl,"
                "text/plain,"
                "*/*"
            )
        )

        if status not in (200, 206):
            return ""

        if "#EXTM3U" not in text.upper():
            return ""

        lines = text.splitlines()
        variants = []

        for index, line in enumerate(lines):
            stream_info = line.strip()

            if not stream_info.upper().startswith(
                "#EXT-X-STREAM-INF"
            ):
                continue

            next_index = index + 1

            while next_index < len(lines):
                media_line = lines[next_index].strip()

                if not media_line:
                    next_index += 1
                    continue

                if media_line.startswith("#"):
                    next_index += 1
                    continue

                media_url = urllib.parse.urljoin(
                    final_url,
                    media_line
                )

                if is_mp4(media_url):
                    break

                if not is_m3u8(media_url):
                    break

                variants.append({
                    "height": get_height(stream_info),
                    "bandwidth": get_bandwidth(stream_info),
                    "url": media_url
                })

                break

        if not variants:
            return final_url if is_m3u8(final_url) else ""

        high_variants = [
            item
            for item in variants
            if item["height"] >= MIN_HEIGHT
        ]

        choices = high_variants or variants

        choices.sort(
            key=lambda item: (
                item["height"],
                item["bandwidth"]
            ),
            reverse=True
        )

        selected = clean(choices[0]["url"])

        if is_mp4(selected):
            return ""

        return selected if is_m3u8(selected) else ""

    except Exception:
        return ""


def fasel_label_score(label):
    label = clean(label).lower()

    height_match = re.search(
        r"(\d{3,4})\s*p?",
        label,
        re.I
    )

    if height_match:
        try:
            height = int(height_match.group(1))

            if height >= 2160:
                return 6000 + height

            if height >= 1080:
                return 5000 + height

            if height >= 720:
                return 4000 + height

            if height >= 480:
                return 3000 + height

            if height >= 360:
                return 2000 + height

            return 1000 + height

        except Exception:
            pass

    if "4k" in label or "uhd" in label:
        return 7000

    if "fullhd" in label or "full hd" in label:
        return 6080

    if "auto" in label or "master" in label:
        return 3500

    return 0


def parse_fasel_response(body):
    results = []

    patterns = [
        (
            r'file\s*:\s*["\']([^"\']+?)["\']'
            r'\s*,\s*label\s*:\s*["\']([^"\']*)["\']'
        ),
        (
            r'["\']file["\']\s*:\s*["\']([^"\']+?)["\']'
            r'\s*,\s*["\']label["\']\s*:\s*["\']([^"\']*)["\']'
        ),
        (
            r'file\s*=\s*["\']([^"\']+?)["\']'
            r'.{0,150}?'
            r'label\s*=\s*["\']([^"\']*)["\']'
        )
    ]

    for pattern in patterns:
        for url, label in re.findall(
            pattern,
            body,
            re.I | re.S
        ):
            url = clean(
                url.replace("\\/", "/")
            )

            label = clean(label)

            if not is_m3u8(url):
                continue

            if is_mp4(url):
                continue

            results.append({
                "url": url,
                "label": label,
                "score": fasel_label_score(label)
            })

    for url in re.findall(
        r'https?://[^\s"\'<>\\]+?\.m3u8'
        r'(?:\?[^\s"\'<>\\]*)?',
        body,
        re.I
    ):
        url = clean(
            url.replace("\\/", "/")
        )

        if not is_m3u8(url):
            continue

        if is_mp4(url):
            continue

        if any(
            item["url"] == url
            for item in results
        ):
            continue

        label = ""

        url_low = url.lower()

        if "1080" in url_low:
            label = "1080p"
        elif "720" in url_low:
            label = "720p"
        elif "480" in url_low:
            label = "480p"
        elif "360" in url_low:
            label = "360p"
        elif "master" in url_low:
            label = "auto"

        results.append({
            "url": url,
            "label": label,
            "score": fasel_label_score(label)
        })

    unique = []
    seen = set()

    for item in results:
        normalized = item["url"].strip()

        if normalized in seen:
            continue

        seen.add(normalized)
        unique.append(item)

    unique.sort(
        key=lambda item: item["score"],
        reverse=True
    )

    return unique


def extract_fasel(
    page_url,
    referer="",
    useragent=""
):
    page_url = clean(page_url)

    if not page_url:
        return ""

    try:
        api_url = (
            FASEL_API
            + urllib.parse.quote(
                page_url,
                safe=""
            )
        )

        fasel_referer = (
            referer
            or "https://fasel-hd.com/"
        )

        status, body, _ = fetch_text(
            api_url,
            timeout=TIMEOUT,
            referer=fasel_referer,
            useragent=useragent or UA,
            accept=(
                "application/json,"
                "text/plain,"
                "text/html,"
                "*/*"
            )
        )

        if status not in (200, 206):
            return ""

        candidates = parse_fasel_response(body)

        for candidate in candidates:
            url = candidate["url"]

            final = resolve_best_quality(
                url,
                referer=fasel_referer,
                useragent=useragent or UA
            )

            if final:
                return final

        return ""

    except Exception:
        return ""


def source_priority(video):
    server = clean(
        video.get("server")
        or ""
    ).lower()

    link = clean(
        video.get("link")
        or video.get("url")
        or video.get("embed")
        or video.get("m3u8")
        or ""
    ).lower()

    text = f"{server} {link}"

    if is_fasel_source(
        video=video
    ):
        return 0

    if "uqload" in text:
        return 1

    if (
        "egybestvid" in text
        or "vidtube" in text
        or "vidspeed" in text
        or "vidoba" in text
        or "streamwish" in text
        or "filelions" in text
        or "earnvids" in text
        or "updown" in text
    ):
        return 2

    if is_m3u8(link):
        return 3

    if is_mp4(link):
        return 100

    return 4


def get_unique_videos(item):
    videos = []
    seen = set()

    for original_index, video in enumerate(
        item.get("videos") or []
    ):
        if not isinstance(video, dict):
            continue

        link = clean(
            video.get("link")
            or video.get("url")
            or video.get("embed")
            or video.get("m3u8")
            or ""
        )

        if not link:
            continue

        # تجاهل MP4 بالكامل
        if is_mp4(link):
            continue

        normalized = link.strip().lower()

        if normalized in seen:
            continue

        seen.add(normalized)

        copied = dict(video)
        copied["_original_index"] = original_index

        videos.append(copied)

    videos.sort(
        key=lambda video: (
            source_priority(video),
            video.get("_original_index", 0)
        )
    )

    return videos


def add_m3u8_url(urls, value):
    value = clean(value)

    if not value:
        return

    if is_mp4(value):
        return

    if not is_m3u8(value):
        return

    urls.append(value)


def get_extractor_urls(data):
    urls = []

    if not isinstance(data, dict):
        return []

    best = data.get("best")

    if isinstance(best, dict):
        add_m3u8_url(
            urls,
            best.get("url")
            or best.get("link")
            or best.get("src")
            or best.get("file")
            or ""
        )

    elif isinstance(best, str):
        add_m3u8_url(
            urls,
            best
        )

    for key in (
        "results",
        "videos",
        "sources",
        "media",
        "links"
    ):
        values = data.get(key) or []

        if not isinstance(values, list):
            continue

        for value in values:
            if isinstance(value, dict):
                add_m3u8_url(
                    urls,
                    value.get("url")
                    or value.get("link")
                    or value.get("src")
                    or value.get("file")
                    or ""
                )

            elif isinstance(value, str):
                add_m3u8_url(
                    urls,
                    value
                )

    output = []
    seen = set()

    for url in urls:
        normalized = url.strip()

        if not normalized:
            continue

        if normalized in seen:
            continue

        seen.add(normalized)
        output.append(normalized)

    return output


def extract_first_working(
    embed,
    referer="",
    useragent=""
):
    embed = clean(embed)

    if not embed:
        return ""

    if is_mp4(embed):
        return ""

    try:
        api_url = (
            EXTRACT
            + urllib.parse.quote(
                embed,
                safe=""
            )
        )

        headers = {
            "User-Agent": useragent or UA,
            "Accept": "application/json,*/*"
        }

        if referer:
            headers["Referer"] = referer

            origin = make_origin(referer)

            if origin:
                headers["Origin"] = origin

        request = urllib.request.Request(
            api_url,
            headers=headers
        )

        with urllib.request.urlopen(
            request,
            timeout=TIMEOUT
        ) as response:
            raw = response.read(
                1000000
            ).decode(
                "utf-8",
                "ignore"
            )

        data = json.loads(raw)

        if not isinstance(data, dict):
            return ""

        urls = get_extractor_urls(data)

        for url in urls:
            final = resolve_best_quality(
                url,
                referer=referer,
                useragent=useragent
            )

            if final:
                return final

    except Exception:
        pass

    return ""


def process(item):
    movie_id = item.get("id", "")

    name = clean(
        item.get("name")
        or item.get("title")
        or item.get("id")
        or "No Name"
    )

    logo = clean(
        item.get("logo")
        or item.get("image")
        or item.get("poster_path")
        or ""
    )

    group = clean(
        item.get("group")
        or item.get("category")
        or "Movies"
    )

    subtitle = clean(
        item.get("subtitle")
        or item.get("quality")
        or ""
    )

    videos = get_unique_videos(item)

    tested = 0

    for video in videos:
        link = clean(
            video.get("link")
            or video.get("url")
            or video.get("embed")
            or video.get("m3u8")
            or ""
        )

        referer = clean(
            video.get("header")
            or video.get("referer")
            or video.get("referrer")
            or ""
        )

        useragent = clean(
            video.get("useragent")
            or video.get("user_agent")
            or ""
        )

        server = clean(
            video.get("server")
            or "Unknown"
        )

        if not link:
            continue

        if is_mp4(link):
            continue

        tested += 1

        # Fasel يستخدم API الخاص بك مباشرة
        if is_fasel_source(
            video=video,
            link=link,
            server=server
        ):
            final = extract_fasel(
                link,
                referer=referer
                or "https://fasel-hd.com/",
                useragent=useragent or UA
            )

            if final:
                return {
                    "ok": True,
                    "row": (
                        name,
                        logo,
                        group,
                        final,
                        subtitle
                    ),
                    "tested": tested,
                    "server": server,
                    "source_type": "fasel-api"
                }

            continue

        # رابط HLS مباشر
        if is_m3u8(link):
            final = resolve_best_quality(
                link,
                referer=referer,
                useragent=useragent
            )

            if final:
                return {
                    "ok": True,
                    "row": (
                        name,
                        logo,
                        group,
                        final,
                        subtitle
                    ),
                    "tested": tested,
                    "server": server,
                    "source_type": "direct-hls"
                }

            continue

        # باقي صفحات المشغلات عبر extractor المحلي
        final = extract_first_working(
            link,
            referer=referer,
            useragent=useragent
        )

        if final:
            return {
                "ok": True,
                "row": (
                    name,
                    logo,
                    group,
                    final,
                    subtitle
                ),
                "tested": tested,
                "server": server,
                "source_type": "local-extractor"
            }

    return {
        "ok": False,
        "id": movie_id,
        "title": name,
        "tested": tested
    }


def sort_rows(rows):
    return sorted(
        rows,
        key=lambda row: row[0].lower()
    )


def write_files(rows):
    ordered_rows = sort_rows(rows)

    m3u_lines = ["#EXTM3U"]
    json_items = []

    for (
        name,
        logo,
        group,
        link,
        subtitle
    ) in ordered_rows:
        if not is_m3u8(link):
            continue

        if is_mp4(link):
            continue

        m3u_lines.append(
            f'#EXTINF:-1 '
            f'tvg-name="{esc(name)}" '
            f'tvg-logo="{esc(logo)}" '
            f'group-title="{esc(group)}",'
            f'{name}'
        )

        m3u_lines.append(link)

        json_items.append({
            "name": name,
            "title": name,
            "subtitle": subtitle,
            "quality": subtitle,
            "image": logo,
            "img": logo,
            "logo": logo,
            "category": group,
            "group": group,
            "link": link,
            "url": link,
            "alt": link,
            "urlobj": [
                {
                    "playUrl": link
                }
            ]
        })

    m3u_tmp = OUT.with_name(
        OUT.name + ".tmp"
    )

    json_tmp = JSON_OUT.with_name(
        JSON_OUT.name + ".tmp"
    )

    m3u_tmp.write_text(
        "\n".join(m3u_lines) + "\n",
        encoding="utf-8"
    )

    json_tmp.write_text(
        json.dumps(
            json_items,
            ensure_ascii=False,
            indent=2
        ),
        encoding="utf-8"
    )

    m3u_tmp.replace(OUT)
    json_tmp.replace(JSON_OUT)


def main():
    items = json.loads(
        SRC.read_text(
            encoding="utf-8"
        )
    )

    if not isinstance(items, list):
        raise TypeError(
            "series_full.json must be a JSON array"
        )

    valid_items = [
        item
        for item in items
        if isinstance(item, dict)
    ]

    rows = []
    ok = 0
    fail = 0

    total_sources = sum(
        len(get_unique_videos(item))
        for item in valid_items
    )

    print(
        "TOTAL MOVIES:",
        len(valid_items),
        flush=True
    )

    print(
        "TOTAL UNIQUE NON-MP4 SOURCES:",
        total_sources,
        flush=True
    )

    OUT.write_text(
        "#EXTM3U\n",
        encoding="utf-8"
    )

    JSON_OUT.write_text(
        "[]",
        encoding="utf-8"
    )

    with ThreadPoolExecutor(
        max_workers=MAX_WORKERS
    ) as executor:
        futures = [
            executor.submit(
                process,
                item
            )
            for item in valid_items
        ]

        for future in as_completed(futures):
            try:
                result = future.result()

            except Exception as error:
                fail += 1

                print(
                    "FAIL EXCEPTION:",
                    repr(error),
                    flush=True
                )

                continue

            if not result or not result.get("ok"):
                fail += 1

                print(
                    "FAIL",
                    result.get("id", "")
                    if result else "",
                    result.get("title", "")
                    if result else "",
                    "| tested:",
                    result.get("tested", 0)
                    if result else 0,
                    flush=True
                )

                continue

            ok += 1
            rows.append(result["row"])

            print(
                "OK",
                ok,
                result["row"][0],
                "| server:",
                result.get("server", ""),
                "| type:",
                result.get("source_type", ""),
                "| tested:",
                result.get("tested", 0),
                flush=True
            )

            if ok % SAVE_EVERY == 0:
                write_files(rows)

                print(
                    "SAVED:",
                    ok,
                    "movies",
                    "| FAIL:",
                    fail,
                    flush=True
                )

    write_files(rows)

    print(
        "DONE OK:",
        ok,
        "FAIL:",
        fail,
        flush=True
    )

    print(
        "M3U:",
        OUT,
        flush=True
    )

    print(
        "JSON:",
        JSON_OUT,
        flush=True
    )


if __name__ == "__main__":
    main()