"""HTTP client for acsearch.info with login, cookie persistence, and rate limiting."""

import json
import os
import re
import time
from pathlib import Path
from urllib.parse import urlencode

import httpx

BASE_URL = "https://www.acsearch.info"
COOKIE_FILE = Path.home() / ".acsearch_cookies.json"
MIN_REQUEST_INTERVAL = 2.0  # seconds between requests


class AcsearchClient:
    """Authenticated HTTP client for acsearch.info."""

    def __init__(
        self,
        username: str | None = None,
        password: str | None = None,
        cookie_file: Path = COOKIE_FILE,
    ):
        self.username = username or os.environ.get("ACSEARCH_USERNAME", "")
        self.password = password or os.environ.get("ACSEARCH_PASSWORD", "")
        self.cookie_file = cookie_file
        self._last_request: float = 0.0
        self._client: httpx.Client | None = None

    @property
    def client(self) -> httpx.Client:
        if self._client is None:
            self._client = self._build_client()
        return self._client

    def _build_client(self) -> httpx.Client:
        """Build an httpx client, loading cookies from file or logging in."""
        client = httpx.Client(
            base_url=BASE_URL,
            headers={
                "User-Agent": (
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                    "AppleWebKit/537.36 (KHTML, like Gecko) "
                    "Chrome/125.0.0.0 Safari/537.36"
                ),
                "Accept": "text/html,application/xhtml+xml",
                "Accept-Language": "en-US,en;q=0.9",
            },
            follow_redirects=True,
            timeout=30.0,
        )

        # Try loading cached cookies
        if self.cookie_file.exists():
            try:
                cookies = json.loads(self.cookie_file.read_text())
                for cookie in cookies:
                    client.cookies.set(
                        cookie["name"], cookie["value"],
                        domain=cookie.get("domain", ""),
                        path=cookie.get("path", "/"),
                    )
                # Verify the session is still valid
                if self._is_authenticated(client):
                    return client
            except (json.JSONDecodeError, KeyError):
                pass

        # Login fresh
        if self.username and self.password:
            self._login(client)
            self._save_cookies(client)
        else:
            raise ValueError(
                "No cached cookies and no ACSEARCH_USERNAME/ACSEARCH_PASSWORD "
                "environment variables set. Cannot authenticate."
            )

        return client

    def _login(self, client: httpx.Client) -> None:
        """Log in to acsearch.info."""
        # Get a fresh session cookie first
        client.get("/login.html")

        resp = client.post(
            "/login.html",
            content=urlencode({
                "login_user_name": self.username,
                "login_password": self.password,
                "login_stayloggedin": "1",
                "form": "login",
            }),
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )
        resp.raise_for_status()

        if "Invalid user name or password" in resp.text:
            raise ValueError("Login failed: invalid credentials")

    def _is_authenticated(self, client: httpx.Client) -> bool:
        """Check if the current session is still valid by verifying price data."""
        try:
            resp = client.get("/search.html?term=test&category=1")
            resp.raise_for_status()
            # Extract initSearchResults and check if prices are visible
            match = re.search(
                r'acsearch\.initSearchResults\s*=\s*(\[[\s\S]*?\]);', resp.text
            )
            if match:
                import json as _json
                data = _json.loads(match.group(1))
                # If any result has a visible price (not "*"), we're authenticated
                return any(
                    r.get("price") and r["price"] != "*"
                    for r in data[:5]
                )
            return False
        except Exception:
            return False

    def _save_cookies(self, client: httpx.Client) -> None:
        """Persist cookies to disk."""
        cookies = []
        for cookie in client.cookies.jar:
            cookies.append({
                "name": cookie.name,
                "value": cookie.value,
                "domain": cookie.domain,
                "path": cookie.path,
            })
        self.cookie_file.write_text(json.dumps(cookies, indent=2))

    def _rate_limit(self) -> None:
        """Enforce minimum interval between requests."""
        now = time.time()
        elapsed = now - self._last_request
        if elapsed < MIN_REQUEST_INTERVAL:
            time.sleep(MIN_REQUEST_INTERVAL - elapsed)
        self._last_request = time.time()

    def _get(self, path: str, **kwargs) -> httpx.Response:
        """GET with rate limiting, auth check, and cookie refresh."""
        self._rate_limit()
        resp = self.client.get(path, **kwargs)

        # If we got redirected to login page, session expired
        if "/login.html" in str(resp.url) or resp.status_code == 403:
            # Re-login
            self._login(self.client)
            self._save_cookies(self.client)
            resp = self.client.get(path, **kwargs)

        resp.raise_for_status()
        # Always save cookies (they might be refreshed)
        self._save_cookies(self.client)
        return resp

    def search(
        self,
        query: str,
        category: int = 1,
        page: int = 1,
        currency: str = "usd",
        order: int = 0,
    ) -> str:
        """Execute a search and return the raw HTML response text.

        Args:
            query: Boolean search query (supports * wildcards, "..." phrases, -exclude)
            category: 0=All, 1=Ancient, 2=Modern, 3=Banknotes, 4=Orders, 8=Literature, 9=Antiquities
            page: Page number (1-indexed, 100 results per page)
            currency: Currency code for prices (usd, eur, chf, gbp)
            order: Sort order (0=relevance, 1=date desc, 2=date asc, 3=price desc, 4=price asc)
        """
        resp = self._get(
            "/search.html",
            params={
                "term": query,
                "category": str(category),
                "page": str(page),
                "currency": currency,
                "order": str(order),
            },
        )
        return resp.text

    def get_lot(self, lot_id: str) -> str:
        """Get the full HTML detail page for a specific lot.

        Args:
            lot_id: The lot/record ID (visible in search result URLs)
        """
        resp = self._get("/search.html", params={"id": lot_id})
        return resp.text

    def get_image(self, image_url: str) -> bytes:
        """Download an image by URL (relative path or absolute)."""
        if image_url.startswith("http"):
            url = image_url
        else:
            url = f"{BASE_URL}{image_url}" if image_url.startswith("/") else f"{BASE_URL}/{image_url}"
        resp = self._get(url) if url.startswith(BASE_URL) else httpx.get(url)
        return resp.content

    def close(self) -> None:
        if self._client:
            self._client.close()
            self._client = None
