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/cimacloud_movies_full.json")
OUT = Path("/var/www/html/Extract/cimacloud_list.m3u")
JSON_OUT = Path("/var/www/html/Extract/cimacloud_list.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"
)


ALLOWED_MEDIA_DOMAINS = (
    "dayt-fer-dw10.gamescdn.online",
    "b2.shahidtv.net",
)


def is_allowed_media_domain(url):
    try:
        host = urllib.parse.urlparse(clean(url)).hostname or ""
    except Exception:
        return False

    host = host.lower().strip(".")

    return any(
        host == domain or host.endswith("." + domain)
        for domain in ALLOWED_MEDIA_DOMAINS
    )


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):
    """يقبل TXT فقط، والاسم محفوظ لتقليل التعديلات في باقي الملف."""
    value = clean(url).strip().lower()

    if not value:
        return False

    try:
        parsed = urllib.parse.urlparse(value)
        path_only = parsed.path.lower()
    except Exception:
        path_only = value.split("?", 1)[0].lower()

    return (
        path_only.endswith(".txt")
        or ".txt?" in value
        or "master.txt" in value
    )

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 _probe_url_without_special_headers(
    url,
    timeout=18,
    read_size=8192
):
    """
    فحص الرابط بالطريقة التي سيعمل بها داخل التطبيق:
    بدون Referer وبدون Origin وبدون هيدرات خاصة بالمصدر.
    """
    headers = {
        "User-Agent": UA,
        "Accept": "*/*",
        "Range": "bytes=0-8191",
        "Connection": "close"
    }

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

    with urllib.request.urlopen(
        request,
        timeout=timeout
    ) as response:
        status = response.status
        final_url = response.geturl()
        content_type = clean(
            response.headers.get("Content-Type", "")
        ).lower()
        content_length = clean(
            response.headers.get("Content-Length", "")
        )
        content_range = clean(
            response.headers.get("Content-Range", "")
        )
        body = response.read(read_size)

    return {
        "status": status,
        "final_url": final_url,
        "content_type": content_type,
        "content_length": content_length,
        "content_range": content_range,
        "body": body
    }


def validate_media_url(
    url,
    referer="",
    useragent="",
    timeout=18
):
    """
    يقبل فقط رابط MP4 أو TXT يعمل كرابط مستقل داخل التطبيق.

    مهم:
    لا يستخدم Referer أو Origin أثناء الفحص، لأن الملف الناتج يحفظ
    الرابط وحده ولا يحفظ هيدرات المصدر.
    """
    url = clean(url)

    if not is_allowed_media_domain(url):
        return False

    if not (is_mp4(url) or is_m3u8(url)):
        return False

    try:
        probe = _probe_url_without_special_headers(
            url,
            timeout=timeout
        )
    except Exception:
        return False

    if probe["status"] not in (200, 206):
        return False

    content_type = probe["content_type"]
    body = probe["body"]

    # رفض صفحات الحماية أو صفحات HTML التي ترجع 200.
    body_start = body[:1024].lstrip().lower()

    if (
        "text/html" in content_type
        or body_start.startswith(b"<!doctype html")
        or body_start.startswith(b"<html")
        or b"<title>cloudflare" in body_start
        or b"access denied" in body_start
    ):
        return False

    if is_mp4(url):
        if not (
            content_type.startswith("video/")
            or "application/octet-stream" in content_type
            or b"ftyp" in body[:128]
        ):
            return False

        # يجب أن يظهر أن الملف ليس صفحة صغيرة أو ردًا وهميًا.
        total_size = 0

        match = re.search(
            r"/(\d+)\s*$",
            probe["content_range"]
        )

        if match:
            try:
                total_size = int(match.group(1))
            except Exception:
                total_size = 0

        if not total_size and probe["content_length"].isdigit():
            total_size = int(probe["content_length"])

        # السماح إذا ظهر توقيع MP4، وإلا نشترط حجمًا معقولًا.
        if b"ftyp" not in body[:128] and 0 < total_size < 500000:
            return False

        return True

    # TXT يجب أن يكون Playlist حقيقيًا.
    playlist_text = body.decode(
        "utf-8",
        "ignore"
    ).lstrip("\ufeff\r\n\t ")

    if not playlist_text.upper().startswith("#EXTM3U"):
        return False

    lines = [
        line.strip()
        for line in playlist_text.splitlines()
        if line.strip() and not line.strip().startswith("#")
    ]

    if not lines:
        return False

    # فحص أول رابط فعلي داخل TXT للتأكد أن القائمة ليست ميتة.
    media_url = urllib.parse.urljoin(
        probe["final_url"],
        lines[0]
    )

    try:
        media_probe = _probe_url_without_special_headers(
            media_url,
            timeout=timeout,
            read_size=2048
        )
    except Exception:
        return False

    if media_probe["status"] not in (200, 206):
        return False

    media_type = media_probe["content_type"]
    media_body = media_probe["body"][:1024].lstrip().lower()

    if (
        "text/html" in media_type
        or media_body.startswith(b"<!doctype html")
        or media_body.startswith(b"<html")
        or b"access denied" in media_body
    ):
        return False

    return True


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 not (is_mp4(master_url) or is_m3u8(master_url)):
        return ""

    if validate_media_url(
        master_url,
        referer=referer,
        useragent=useragent
    ):
        return master_url

    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_mp4(url) or is_m3u8(url)):
                continue

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

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

        if not (is_mp4(url) or is_m3u8(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_mp4(link) or is_m3u8(link):
        return 1

    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

        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 not (is_mp4(value) or 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) or is_m3u8(embed):
        return (
            embed
            if validate_media_url(
                embed,
                referer=referer,
                useragent=useragent
            )
            else ""
        )

    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

        # قبول روابط MP4/TXT فقط من الدومينات المحددة
        if (
            (is_mp4(link) or is_m3u8(link))
            and not is_allowed_media_domain(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

        # رابط مباشر: MP4 أو TXT، ويُحفظ فقط إذا كان يعمل
        if is_mp4(link) or is_m3u8(link):
            if not validate_media_url(
                link,
                referer=referer,
                useragent=useragent
            ):
                continue

            return {
                "ok": True,
                "row": (
                    name,
                    logo,
                    group,
                    link,
                    subtitle
                ),
                "tested": tested,
                "server": server,
                "source_type": (
                    "direct-mp4"
                    if is_mp4(link)
                    else "direct-txt"
                )
            }

        # باقي صفحات المشغلات عبر 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_mp4(link) or is_m3u8(link)):
            continue

        if not is_allowed_media_domain(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 MP4/TXT SOURCES TO TEST:",
        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()