import re
from pathlib import Path
from urllib.parse import urljoin

from bs4 import BeautifulSoup

from ..context import AppContext
from ..models import MovieMetadata
from .base import Scraper


class JavbusScraper(Scraper):
    name = "javbus"
    base_url = "https://www.javbus.com/"

    def fetch(self, number: str, ctx: AppContext) -> MovieMetadata | None:
        cookies = ctx.config.javbus_cookies()
        html = self._fetch_page(number, ctx, cookies)
        if not html:
            if ctx.debug:
                print(f"[DEBUG][javbus] page not found for {number}")
            return None
        data = self._parse(html, number)
        if data and data.is_valid():
            if ctx.debug:
                print(
                    f"[DEBUG][javbus] parsed ok number={data.number} title={data.title[:40]} "
                    f"cover={'yes' if data.cover else 'no'} actors={len(data.actor)}"
                )
            return data
        if ctx.debug:
            print(f"[DEBUG][javbus] parsed invalid number={number}")
        return None

    def _fetch_page(self, number: str, ctx: AppContext, cookies: dict) -> str:
        keys = (number, number.replace("-", "_"))
        for idx, key in enumerate(keys):
            url = urljoin(self.base_url, key)
            if ctx.debug:
                print(f"[DEBUG][javbus] GET {url}")
            try:
                text = ctx.http.get_text(url, cookies=cookies, headers=self._headers())
            except RuntimeError as err:
                if ctx.debug:
                    print(f"[DEBUG][javbus] request failed {url}: {err}")
                # Network/proxy failures should not fan out to alternate URL forms.
                return ""
            self._dump_html(ctx, f"{key}.html", text)
            if self._looks_like_not_found(text):
                if ctx.debug:
                    print(f"[DEBUG][javbus] filtered as 404: {url}")
                # Try underscore fallback only when primary URL returned a not-found page.
                if idx == 0 and keys[0] != keys[1]:
                    continue
                return ""
            return text
        return ""

    @staticmethod
    def _looks_like_not_found(html: str) -> bool:
        return "404" in html and "JavBus" not in html

    def _parse(self, html: str, requested_number: str) -> MovieMetadata:
        soup = BeautifulSoup(html, "lxml")
        title_raw = soup.select_one("div.container h3")
        title_text = title_raw.get_text(strip=True) if title_raw else ""
        if not title_text:
            title_node = soup.select_one("title")
            title_text = title_node.get_text(strip=True) if title_node else ""

        cover_node = soup.select_one("a.bigImage")
        cover = cover_node.get("href", "") if cover_node else ""

        actor_nodes = soup.select("div.star-name a, a.avatar-box .photo-info span")
        actors = list(dict.fromkeys(n.get_text(strip=True) for n in actor_nodes if n.get_text(strip=True)))
        tags = [n.get_text(strip=True) for n in soup.select("span.genre") if n.get_text(strip=True)]

        info = self._extract_info_map(soup)
        number = info.get("識別碼", "") or info.get("识别码", "") or requested_number
        release = info.get("發行日期", "") or info.get("发行日期", "")
        studio = info.get("製作商", "") or info.get("制作商", "")
        director = info.get("導演", "") or info.get("导演", "")
        runtime = info.get("長度", "") or info.get("长度", "")
        year_match = re.search(r"(\d{4})", release)
        year = year_match.group(1) if year_match else ""

        title = title_text
        if number and title.startswith(number):
            title = title[len(number):].strip(" -")
        if not title:
            title = requested_number

        return MovieMetadata(
            title=title,
            number=number,
            studio=studio,
            year=year,
            runtime=runtime,
            director=director,
            actor=actors,
            release=release,
            cover=cover,
            tag=tags,
            website=urljoin(self.base_url, requested_number),
            source=self.name,
        )

    @staticmethod
    def _dump_html(ctx: AppContext, filename: str, html: str) -> None:
        if not ctx.debug:
            return
        path = Path(ctx.debug_dir) / filename
        path.write_text(html, encoding="utf-8", errors="ignore")

    @staticmethod
    def _headers() -> dict:
        return {
            "accept-language": "zh-CN,zh;q=0.9,zh-HK;q=0.8",
            "cache-control": "max-age=0",
            "sec-ch-ua": '"Chromium";v="117", "Not;A=Brand";v="8"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            "user-agent": (
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/128.0.0.0 Safari/537.36"
            ),
        }

    @staticmethod
    def _extract_info_map(soup: BeautifulSoup) -> dict[str, str]:
        info: dict[str, str] = {}
        for p in soup.select("div.info p"):
            span = p.select_one("span.header")
            if not span:
                continue
            key = span.get_text(strip=True).replace(":", "").replace("：", "")
            value = p.get_text(" ", strip=True).replace(span.get_text(strip=True), "", 1).strip()
            info[key] = value
        return info
