"""Download videos from sora.chatgpt.com share pages."""

import re
import json
import logging
from pathlib import Path
from datetime import datetime
from urllib.parse import urlparse, quote

import requests
from curl_cffi import requests as cffi_requests
from bs4 import BeautifulSoup

from backend.core.config import settings

logger = logging.getLogger("viral_social.sora")


class SoraDownloader:
    """Download videos from sora.chatgpt.com share pages."""

    SORA_DOMAIN = "sora.chatgpt.com"
    SORAVDL_API = "https://soravdl.com/api/proxy/video"

    HEADERS = {
        "User-Agent": (
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/131.0.0.0 Safari/537.36"
        ),
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Sec-Ch-Ua": '"Chromium";v="131", "Not_A Brand";v="24"',
        "Sec-Ch-Ua-Mobile": "?0",
        "Sec-Ch-Ua-Platform": '"macOS"',
        "Upgrade-Insecure-Requests": "1",
    }

    def __init__(self):
        self.base_dir = settings.download_path / "sora"
        self.base_dir.mkdir(parents=True, exist_ok=True)

    @staticmethod
    def is_sora_url(url: str) -> bool:
        """Check if a URL is a sora.chatgpt.com share page."""
        try:
            parsed = urlparse(url)
            return parsed.hostname in (
                "sora.chatgpt.com",
                "www.sora.chatgpt.com",
                "sora.com",
            )
        except Exception:
            return False

    @staticmethod
    def _extract_sora_id(url: str) -> str | None:
        """Extract the Sora video ID from a Sora share URL.

        Supports formats:
        - /p/s_69a5dae... (public share)
        - /d/gen_01kk9... (direct/private)
        """
        try:
            parsed = urlparse(url)
            path_parts = parsed.path.strip("/").split("/")
            for part in path_parts:
                if part.startswith("s_") or part.startswith("gen_"):
                    return part
            return None
        except Exception:
            return None

    def download(self, url: str, quality: str = "best") -> dict:
        """
        Download a video from a Sora share page.

        Tries soravdl.com API first (watermark-free), falls back to direct extraction.
        Returns dict matching VideoDownloader.download() format.
        """
        try:
            # Check if it's a private /d/gen_ URL that might need share conversion
            sora_id = self._extract_sora_id(url)
            is_private = "/d/gen_" in url

            # Strategy 1: soravdl.com API (watermark-free) - only for s_ IDs
            if sora_id and sora_id.startswith("s_"):
                result = self._download_via_soravdl(sora_id, url)
                if result and result["success"]:
                    return result
                logger.warning("soravdl.com failed, falling back to direct extraction")

            # Strategy 2: Direct extraction from Sora page
            html = self._fetch_page(url)
            if not html:
                if is_private:
                    return self._error(
                        "Este link do Sora e privado (/d/gen_...) e requer login. "
                        "Use o link de compartilhamento publico (/p/s_...) em vez disso. "
                        "No Sora, clique em 'Share' para copiar o link publico."
                    )
                return self._error("Falha ao acessar a pagina do Sora")

            soup = BeautifulSoup(html, "html.parser")
            video_url = self._extract_video_url(soup, url)

            if not video_url:
                return self._error(
                    "URL do vídeo não encontrada na página do Sora. "
                    "O link pode ter expirado ou a página requer autenticação."
                )

            title = self._extract_title(soup)
            return self._download_file(video_url, title)

        except Exception as e:
            logger.error(f"Sora download error: {e}")
            return self._error(f"Erro no download do Sora: {str(e)}")

    def _download_via_soravdl(self, sora_id: str, original_url: str) -> dict | None:
        """Download watermark-free video via soravdl.com proxy API."""
        try:
            api_url = f"{self.SORAVDL_API}/{sora_id}"
            logger.info(f"Sora: trying soravdl.com for {sora_id}")

            resp = cffi_requests.get(
                api_url,
                headers={
                    "Referer": f"https://soravdl.com/v/{sora_id}",
                },
                impersonate="chrome",
                timeout=120,
                stream=True,
            )
            resp.raise_for_status()

            # Verify we got a video response
            content_type = resp.headers.get("content-type", "")
            if "video" not in content_type and "octet-stream" not in content_type:
                logger.warning(f"soravdl.com returned non-video content: {content_type}")
                return None

            # Extract title from original page (optional, don't fail)
            title = f"sora_{sora_id}"
            try:
                html = self._fetch_page(original_url)
                if html:
                    soup = BeautifulSoup(html, "html.parser")
                    title = self._extract_title(soup)
            except Exception:
                pass

            date_str = datetime.now().strftime("%Y%m%d")
            safe_title = self._sanitize_filename(title)
            filename = f"{date_str}_{safe_title}.mp4"
            file_path = self.base_dir / filename

            # Avoid overwriting
            counter = 1
            while file_path.exists():
                filename = f"{date_str}_{safe_title}_{counter}.mp4"
                file_path = self.base_dir / filename
                counter += 1

            # Stream download
            total_size = 0
            with open(file_path, "wb") as f:
                for chunk in resp.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        total_size += len(chunk)

            if total_size < 1000:
                # Too small, probably an error page
                file_path.unlink(missing_ok=True)
                logger.warning(f"soravdl.com returned tiny file ({total_size} bytes)")
                return None

            logger.info(
                f"Sora download (soravdl, no watermark): {filename} "
                f"({self._format_size(total_size)})"
            )

            return {
                "success": True,
                "path": str(file_path),
                "filename": filename,
                "size": total_size,
                "size_formatted": self._format_size(total_size),
                "duration": 0,
                "title": title,
                "error": None,
            }

        except requests.exceptions.HTTPError as e:
            logger.warning(f"soravdl.com HTTP error: {e.response.status_code}")
            return None
        except Exception as e:
            logger.warning(f"soravdl.com error: {e}")
            return None

    # ── Page fetching ────────────────────────────────────────────

    def _fetch_page(self, url: str) -> str | None:
        """Fetch HTML content of the share page using curl_cffi to bypass Cloudflare."""
        try:
            resp = cffi_requests.get(url, impersonate="chrome", timeout=30)
            resp.raise_for_status()
            # Detect login redirect (private URLs)
            if "/login" in str(resp.url):
                logger.warning(f"Sora redirected to login for {url}")
                return None
            return resp.text
        except Exception as e:
            logger.error(f"Sora fetch error: {e}")
            return None

    # ── Video URL extraction (multi-strategy) ────────────────────

    def _extract_video_url(self, soup: BeautifulSoup, url: str) -> str | None:
        """Try multiple extraction strategies to find the video URL."""

        # Strategy 1: Open Graph meta tags
        video_url = self._extract_from_og_tags(soup)
        if video_url:
            logger.info("Sora: video URL found via OG tags")
            return video_url

        # Strategy 2: __NEXT_DATA__ JSON (Next.js)
        video_url = self._extract_from_next_data(soup)
        if video_url:
            logger.info("Sora: video URL found via __NEXT_DATA__")
            return video_url

        # Strategy 3: Regex scan all <script> tags
        video_url = self._extract_from_scripts(soup)
        if video_url:
            logger.info("Sora: video URL found via script scanning")
            return video_url

        # Strategy 4: <video> / <source> elements
        video_url = self._extract_from_video_tags(soup)
        if video_url:
            logger.info("Sora: video URL found via <video> tag")
            return video_url

        logger.warning(f"Sora: no video URL found for {url}")
        return None

    def _extract_from_og_tags(self, soup: BeautifulSoup) -> str | None:
        """Extract video URL from Open Graph meta tags."""
        for prop in ("og:video", "og:video:url", "og:video:secure_url"):
            tag = soup.find("meta", property=prop)
            if tag and tag.get("content"):
                content = tag["content"]
                if self._looks_like_video_url(content):
                    return content
        return None

    def _extract_from_next_data(self, soup: BeautifulSoup) -> str | None:
        """Extract video URL from __NEXT_DATA__ script tag (Next.js)."""
        script = soup.find("script", id="__NEXT_DATA__")
        if not script or not script.string:
            return None

        try:
            data = json.loads(script.string)
            page_props = data.get("props", {}).get("pageProps", {})

            # Search common nested locations for video data
            for key in ("video", "post", "generation", "data", "result"):
                obj = page_props.get(key, {})
                if isinstance(obj, dict):
                    # Look for encodings with quality tiers
                    encodings = obj.get("encodings", {})
                    if encodings:
                        for q in ("source", "hd", "md", "ld"):
                            enc = encodings.get(q, {})
                            path = enc.get("path") or enc.get("url")
                            if path and self._looks_like_video_url(path):
                                return path

                    # Direct video_url or url field
                    for field in ("video_url", "url", "download_url", "mp4_url"):
                        val = obj.get(field)
                        if val and self._looks_like_video_url(val):
                            return val

            # Fallback: recursively search for any .mp4 URL in the JSON
            return self._find_mp4_in_dict(data)

        except (json.JSONDecodeError, KeyError) as e:
            logger.debug(f"Sora __NEXT_DATA__ parse error: {e}")
            return None

    def _extract_from_scripts(self, soup: BeautifulSoup) -> str | None:
        """Scan script tags for video URLs, including escaped JSON data."""
        from urllib.parse import unquote

        for script in soup.find_all("script"):
            text = script.string or ""
            if not text:
                continue

            # Strategy A: Find encodings in escaped JSON (Sora uses this)
            # Pattern: \"encodings\":{\"source\":{\"path\":\"URL\"
            if "encodings" in text:
                # Try source quality first, then md, then ld
                for quality in ("source", "md", "ld"):
                    pattern = (
                        r'[\\]?"' + quality + r'[\\]?"'
                        r'\s*:\s*[\\]?\{\s*[\\]?"path[\\]?"'
                        r'\s*:\s*[\\]?"(https://[^"\\]+(?:\\u[0-9a-fA-F]{4}[^"\\]*)*)[\\]?"'
                    )
                    match = re.search(pattern, text)
                    if match:
                        video_url = match.group(1)
                        # Decode unicode escapes (\u0026 -> &)
                        video_url = video_url.encode().decode("unicode_escape")
                        # Decode URL-encoded chars (%2F -> /)
                        video_url = unquote(video_url)
                        logger.info(f"Sora: found {quality} encoding URL")
                        return video_url

            # Strategy B: Direct regex for videos.openai.com URLs
            openai_pattern = re.compile(
                r'https://videos\.openai\.com/[^\s"\'\\>}]+'
            )
            matches = openai_pattern.findall(text)
            for match in matches:
                # Skip thumbnail URLs
                if "link_thumbnail" not in match and "raw" in match:
                    clean = match.replace("\\u0026", "&")
                    return unquote(clean)

        return None

    def _extract_from_video_tags(self, soup: BeautifulSoup) -> str | None:
        """Extract from <video> or <source> elements."""
        for video in soup.find_all("video"):
            src = video.get("src")
            if src and self._looks_like_video_url(src):
                return src

        for source in soup.find_all("source"):
            src = source.get("src")
            if src and self._looks_like_video_url(src):
                return src

        return None

    # ── Title extraction ─────────────────────────────────────────

    def _extract_title(self, soup: BeautifulSoup) -> str:
        """Extract video title from OG tags or page title."""
        og_title = soup.find("meta", property="og:title")
        if og_title and og_title.get("content"):
            return og_title["content"]

        title_tag = soup.find("title")
        if title_tag and title_tag.string:
            return title_tag.string.strip()

        return "sora_video"

    # ── File download ────────────────────────────────────────────

    def _download_file(self, video_url: str, title: str) -> dict:
        """Stream-download the MP4 file and save to downloads/sora/."""
        try:
            resp = cffi_requests.get(
                video_url,
                impersonate="chrome",
                timeout=120,
                stream=True,
            )
            resp.raise_for_status()

            date_str = datetime.now().strftime("%Y%m%d")
            safe_title = self._sanitize_filename(title)
            filename = f"{date_str}_{safe_title}.mp4"
            file_path = self.base_dir / filename

            # Avoid overwriting
            counter = 1
            while file_path.exists():
                filename = f"{date_str}_{safe_title}_{counter}.mp4"
                file_path = self.base_dir / filename
                counter += 1

            # Stream download
            total_size = 0
            with open(file_path, "wb") as f:
                for chunk in resp.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        total_size += len(chunk)

            logger.info(f"Sora download complete: {filename} ({self._format_size(total_size)})")

            return {
                "success": True,
                "path": str(file_path),
                "filename": filename,
                "size": total_size,
                "size_formatted": self._format_size(total_size),
                "duration": 0,
                "title": title,
                "error": None,
            }

        except Exception as e:
            logger.error(f"Sora file download error: {e}")
            return self._error(f"Falha ao baixar o arquivo de vídeo: {str(e)}")

    # ── Helpers ───────────────────────────────────────────────────

    @staticmethod
    def _looks_like_video_url(url: str) -> bool:
        """Check if URL looks like a direct video file."""
        if not url or not url.startswith("http"):
            return False
        url_lower = url.lower().split("?")[0]
        return (
            url_lower.endswith(".mp4")
            or url_lower.endswith(".webm")
            or "videos.openai.com" in url_lower
            or "blob.core.windows.net" in url_lower
        )

    @staticmethod
    def _sanitize_filename(name: str, max_length: int = 50) -> str:
        """Remove unsafe characters from filename."""
        import unicodedata

        # Normalize unicode
        name = unicodedata.normalize("NFKD", name)
        # Keep only safe characters
        name = re.sub(r"[^\w\s-]", "", name)
        name = re.sub(r"\s+", "_", name.strip())
        return name[:max_length] or "sora_video"

    @staticmethod
    def _format_size(size_bytes: int) -> str:
        """Format file size."""
        if size_bytes == 0:
            return "0 B"
        units = ["B", "KB", "MB", "GB"]
        i = 0
        size = float(size_bytes)
        while size >= 1024 and i < len(units) - 1:
            size /= 1024
            i += 1
        return f"{size:.1f} {units[i]}"

    @staticmethod
    def _error(message: str) -> dict:
        """Return a standard error dict."""
        return {
            "success": False,
            "path": "",
            "filename": "",
            "size": 0,
            "size_formatted": "",
            "duration": 0,
            "title": "",
            "error": message,
        }

    def _find_mp4_in_dict(self, obj, depth: int = 0) -> str | None:
        """Recursively search a dict/list for an MP4 URL."""
        if depth > 10:
            return None
        if isinstance(obj, str):
            if self._looks_like_video_url(obj):
                return obj
        elif isinstance(obj, dict):
            for v in obj.values():
                result = self._find_mp4_in_dict(v, depth + 1)
                if result:
                    return result
        elif isinstance(obj, list):
            for item in obj:
                result = self._find_mp4_in_dict(item, depth + 1)
                if result:
                    return result
        return None
