from flask import Flask, request, jsonify
from flask_cors import CORS
import yt_dlp
import requests
from bs4 import BeautifulSoup
from urllib.parse import quote_plus
from concurrent.futures import ThreadPoolExecutor, as_completed
import tempfile
import hashlib
import os
import time
import re

app = Flask(__name__)
CORS(app)

API_TOKEN = "BTM-YTDLP-2026-SECURE-9K7XQ"

SUPPORTED_SEARCH_SITES = {
    "xvideos": "https://www.xvideos.com/?k={query}&p={page}",
    "xnxx": "https://www.xnxx.com/search/{query}/{page}",
    "pornhub": "https://www.pornhub.com/video/search?search={query}&page={page}"
}

DEFAULT_HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/122.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9"
}


def success(data, status=200):
    return jsonify({"ok": True, **data}), status


def error(code, message, status=400):
    return jsonify({
        "ok": False,
        "error": {
            "code": code,
            "message": message
        }
    }), status


def validate_token():
    token = request.headers.get("X-API-Token") or request.args.get("token")

    if request.method == "POST" and request.is_json:
        body = request.get_json(silent=True) or {}
        token = body.get("token", token)

    return token == API_TOKEN


@app.before_request
def auth():
    if request.path in ["/", "/health"]:
        return

    if not validate_token():
        return error("INVALID_TOKEN", "Invalid API token.", 401)


def clean_text(value):
    if not value:
        return None

    return re.sub(r"\s+", " ", value).strip()


def make_json_safe(obj):
    if isinstance(obj, dict):
        return {
            str(k): make_json_safe(v)
            for k, v in obj.items()
            if not callable(v)
        }

    if isinstance(obj, list):
        return [make_json_safe(v) for v in obj]

    if isinstance(obj, tuple):
        return [make_json_safe(v) for v in obj]

    if isinstance(obj, (str, int, float, bool)) or obj is None:
        return obj

    return str(obj)


def extract_duration_from_text(text):
    if not text:
        return None

    match = re.search(r"(\d{1,2}:\d{2}(?::\d{2})?)", text)

    if match:
        return match.group(1)

    return None


def extract_views_from_text(text):
    if not text:
        return None

    patterns = [
        r"([\d.,]+\s*[KMB]?)\s*views",
        r"([\d.,]+\s*[KMB]?)\s*vues",
        r"([\d.,]+\s*[KMB]?)\s*view",
        r"([\d.,]+\s*[KMB]?)\s*vue"
    ]

    for pattern in patterns:
        match = re.search(pattern, text, re.IGNORECASE)

        if match:
            value = match.group(1)
            value = value.replace(" ", "")
            value = value.upper()
            return value

    return None


@app.route("/", methods=["GET"])
def root():
    return success({
        "name": "BotMada yt-dlp API",
        "version": "3.3",
        "routes": {
            "search": "/api/search",
            "download": "/download",
            "health": "/health"
        }
    })


@app.route("/health", methods=["GET"])
def health():
    return success({
        "status": "running",
        "timestamp": int(time.time())
    })


def scrape_xvideos(query, page, max_results):
    results = []

    try:
        url = SUPPORTED_SEARCH_SITES["xvideos"].format(
            query=quote_plus(query),
            page=max(page - 1, 0)
        )

        response = requests.get(url, headers=DEFAULT_HEADERS, timeout=20)
        soup = BeautifulSoup(response.text, "html.parser")

        videos = soup.select("div.thumb-block")

        for item in videos[:max_results]:
            title_tag = item.select_one("p.title a")

            if not title_tag:
                continue

            title = title_tag.get("title") or title_tag.get_text(strip=True)
            href = title_tag.get("href")

            if not href:
                continue

            if href.startswith("/"):
                href = "https://www.xvideos.com" + href

            img = item.select_one("img")

            thumbnail = None
            if img:
                thumbnail = (
                    img.get("data-src")
                    or img.get("src")
                    or img.get("data-original")
                )

            block_text = clean_text(item.get_text(" ", strip=True))

            duration = None
            duration_tag = item.select_one(".duration")
            if duration_tag:
                duration = clean_text(duration_tag.get_text(strip=True))

            if not duration:
                duration = extract_duration_from_text(block_text)

            views = None
            metadata_tag = item.select_one(".metadata")
            if metadata_tag:
                views = extract_views_from_text(
                    metadata_tag.get_text(" ", strip=True)
                )

            if not views:
                views = extract_views_from_text(block_text)

            results.append({
                "site": "xvideos",
                "title": clean_text(title),
                "url": href,
                "thumbnail": thumbnail,
                "duration": duration,
                "views": views
            })

    except Exception as e:
        print("xvideos scrape error:", e)

    return results


def scrape_xnxx(query, page, max_results):
    results = []

    try:
        url = SUPPORTED_SEARCH_SITES["xnxx"].format(
            query=quote_plus(query),
            page=page
        )

        response = requests.get(url, headers=DEFAULT_HEADERS, timeout=20)
        soup = BeautifulSoup(response.text, "html.parser")

        videos = soup.select("div.thumb-block")

        for item in videos[:max_results]:
            title_tag = item.select_one("p.title a")

            if not title_tag:
                continue

            title = title_tag.get("title") or title_tag.get_text(strip=True)
            href = title_tag.get("href")

            if not href:
                continue

            if href.startswith("/"):
                href = "https://www.xnxx.com" + href

            img = item.select_one("img")

            thumbnail = None
            if img:
                thumbnail = (
                    img.get("data-src")
                    or img.get("src")
                    or img.get("data-original")
                )

            block_text = clean_text(item.get_text(" ", strip=True))

            duration = None
            duration_tag = item.select_one(".duration")
            if duration_tag:
                duration = clean_text(duration_tag.get_text(strip=True))

            if not duration:
                duration = extract_duration_from_text(block_text)

            views = extract_views_from_text(block_text)

            results.append({
                "site": "xnxx",
                "title": clean_text(title),
                "url": href,
                "thumbnail": thumbnail,
                "duration": duration,
                "views": views
            })

    except Exception as e:
        print("xnxx scrape error:", e)

    return results


def scrape_pornhub(query, page, max_results):
    results = []

    try:
        url = SUPPORTED_SEARCH_SITES["pornhub"].format(
            query=quote_plus(query),
            page=page
        )

        response = requests.get(url, headers=DEFAULT_HEADERS, timeout=20)
        soup = BeautifulSoup(response.text, "html.parser")

        videos = soup.select("li.videoblock")

        for item in videos[:max_results]:
            title_tag = (
                item.select_one("span.title a")
                or item.select_one(".title a")
                or item.select_one("a")
            )

            if not title_tag:
                continue

            title = title_tag.get("title") or title_tag.get_text(strip=True)
            href = title_tag.get("href")

            if not href:
                continue

            if href.startswith("/"):
                href = "https://www.pornhub.com" + href

            img = item.select_one("img")

            thumbnail = None
            if img:
                thumbnail = (
                    img.get("data-src")
                    or img.get("src")
                    or img.get("data-mediumthumb")
                    or img.get("data-thumb_url")
                )

            block_text = clean_text(item.get_text(" ", strip=True))

            duration = None
            duration_tag = (
                item.select_one(".duration")
                or item.select_one("var.duration")
            )

            if duration_tag:
                duration = clean_text(duration_tag.get_text(strip=True))

            if not duration:
                duration = extract_duration_from_text(block_text)

            views = None
            views_tag = (
                item.select_one(".views")
                or item.select_one(".videoDetailsBlock .views")
            )

            if views_tag:
                views = extract_views_from_text(
                    views_tag.get_text(" ", strip=True)
                )

            if not views:
                views = extract_views_from_text(block_text)

            results.append({
                "site": "pornhub",
                "title": clean_text(title),
                "url": href,
                "thumbnail": thumbnail,
                "duration": duration,
                "views": views
            })

    except Exception as e:
        print("pornhub scrape error:", e)

    return results


def scrape_site(site, query, page, max_results):
    if site == "xvideos":
        return scrape_xvideos(query, page, max_results)

    if site == "xnxx":
        return scrape_xnxx(query, page, max_results)

    if site == "pornhub":
        return scrape_pornhub(query, page, max_results)

    return []


@app.route("/api/search", methods=["GET"])
def api_search():
    query = request.args.get("query", "").strip()
    sites = request.args.get("sites", "xvideos,xnxx,pornhub")
    page = int(request.args.get("page", 1))
    max_results = int(request.args.get("max_results", 10))

    if not query:
        return error("QUERY_REQUIRED", "query parameter is required.")

    selected_sites = [
        s.strip().lower()
        for s in sites.split(",")
        if s.strip().lower() in SUPPORTED_SEARCH_SITES
    ]

    if not selected_sites:
        return error(
            "INVALID_SITE",
            "Aucun site valide. Sites disponibles: xvideos,xnxx,pornhub"
        )

    all_results = []

    with ThreadPoolExecutor(max_workers=len(selected_sites)) as executor:
        futures = [
            executor.submit(
                scrape_site,
                site,
                query,
                page,
                max_results
            )
            for site in selected_sites
        ]

        for future in as_completed(futures):
            try:
                all_results.extend(future.result())
            except Exception as e:
                print("future error:", e)

    return success({
        "query": query,
        "page": page,
        "sites": selected_sites,
        "count": len(all_results),
        "results": all_results[:max_results]
    })


def extract_video(url, options):
    ydl_opts = {
        "quiet": True,
        "no_warnings": True,
        "skip_download": True,
        "nocheckcertificate": True,
        "extract_flat": False,
        "http_headers": {
            "User-Agent": options.get(
                "user_agent",
                DEFAULT_HEADERS["User-Agent"]
            )
        }
    }

    if options.get("proxy"):
        ydl_opts["proxy"] = options["proxy"]

    if options.get("cookiefile"):
        ydl_opts["cookiefile"] = options["cookiefile"]

    if options.get("format"):
        ydl_opts["format"] = options["format"]

    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        return ydl.extract_info(url, download=False)


@app.route("/download", methods=["POST"])
def download():
    cookies_path = None

    try:
        if request.content_type and "multipart/form-data" in request.content_type:
            url = request.form.get("url", "").strip()
            format_selector = request.form.get(
                "format",
                "bestvideo+bestaudio/best"
            )
            proxy = request.form.get("proxy", "")
            user_agent = request.form.get(
                "user_agent",
                DEFAULT_HEADERS["User-Agent"]
            )

            cookie_file = request.files.get("cookie_file")

            if cookie_file:
                temp_dir = tempfile.gettempdir()
                cookies_path = os.path.join(
                    temp_dir,
                    f"cookies_{hashlib.md5(str(time.time()).encode()).hexdigest()}.txt"
                )
                cookie_file.save(cookies_path)

        else:
            data = request.get_json(silent=True) or {}

            url = data.get("url", "").strip()
            format_selector = data.get(
                "format",
                "bestvideo+bestaudio/best"
            )
            proxy = data.get("proxy", "")
            user_agent = data.get(
                "user_agent",
                DEFAULT_HEADERS["User-Agent"]
            )
            cookies_path = data.get("cookie_file", None)

        if not url:
            return error("URL_REQUIRED", "url is required.")

        info = extract_video(
            url,
            {
                "format": format_selector,
                "proxy": proxy,
                "user_agent": user_agent,
                "cookiefile": cookies_path
            }
        )

        full_info = make_json_safe(info)

        return success({
            "message": "Informations complètes récupérées avec succès.",
            "info": full_info
        })

    except Exception as e:
        return error("EXTRACTION_ERROR", str(e), 500)

    finally:
        if cookies_path and os.path.exists(cookies_path):
            try:
                os.remove(cookies_path)
            except:
                pass


@app.errorhandler(404)
def not_found(e):
    return error("NOT_FOUND", "Route introuvable.", 404)


@app.errorhandler(500)
def internal_error(e):
    return error("SERVER_ERROR", "Internal server error.", 500)


if __name__ == "__main__":
    app.run(
        host="0.0.0.0",
        port=5000,
        debug=True
    )