"""John Pye Auctions web scraper using Playwright for Cloudflare bypass."""

import json
import re
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any

from bs4 import BeautifulSoup
from playwright.sync_api import Browser, Page, sync_playwright


@dataclass
class ScrapedLotItem:
    """A lot item scraped from an invoice."""

    lot_number: str
    description: str
    listing_url: str | None
    hammer_price: int  # pence
    vat: int  # pence
    subtotal: int  # pence


@dataclass
class ScrapedInvoice:
    """An invoice scraped from John Pye."""

    invoice_number: str
    date: datetime
    site: str
    status: str  # "new", "paid"
    delivery_status: str | None
    total_pence: int
    lot_items: list[ScrapedLotItem]
    net_hammer_pence: int
    delivery_pence: int
    buyers_premium_pence: int
    total_vat_pence: int
    grand_total_pence: int


class JohnPyeScraper:
    """Scraper for John Pye Auctions website using Playwright."""

    BASE_URL = "https://www.johnpyeauctions.co.uk"

    def __init__(self, browser: Browser, cookies: list[dict[str, Any]] | None = None):
        """Initialize with Playwright browser.

        Args:
            browser: Playwright browser instance.
            cookies: List of cookie dicts in Playwright format.
        """
        self.browser = browser
        self.context = browser.new_context()
        if cookies:
            self.context.add_cookies(cookies)
        self.page: Page = self.context.new_page()

    @classmethod
    def from_cookie_file(cls, cookie_path: str | Path) -> "JohnPyeScraper":
        """Create scraper loading cookies from Integuru-format JSON file.

        Args:
            cookie_path: Path to cookies.json file.
        """
        with open(cookie_path) as f:
            cookie_list = json.load(f)

        # Convert to Playwright cookie format
        pw_cookies = []
        for c in cookie_list:
            domain = c.get("domain", "")
            if "johnpye" in domain.lower():
                pw_cookie: dict[str, Any] = {
                    "name": c["name"],
                    "value": c["value"],
                    "domain": domain,
                    "path": c.get("path", "/"),
                }
                # Add optional fields if present
                if "expires" in c and c["expires"] != -1:
                    pw_cookie["expires"] = c["expires"]
                if c.get("secure"):
                    pw_cookie["secure"] = True
                if c.get("httpOnly"):
                    pw_cookie["httpOnly"] = True
                pw_cookies.append(pw_cookie)

        playwright = sync_playwright().start()
        browser = playwright.chromium.launch(headless=True)
        scraper = cls(browser, pw_cookies)
        scraper._playwright = playwright  # Keep reference for cleanup
        return scraper

    @classmethod
    def from_saved_state(
        cls, state_path: str | Path | None = None, headless: bool = False
    ) -> "JohnPyeScraper":
        """Create scraper from saved browser state (preferred method).

        Args:
            state_path: Path to state JSON. Defaults to ~/.johnpye_state.json
            headless: Run in headless mode (may be blocked by Cloudflare).
        """
        state_path = (
            Path.home() / ".johnpye_state.json" if state_path is None else Path(state_path)
        )

        if not state_path.exists():
            raise FileNotFoundError(
                f"No saved state at {state_path}. "
                "Run: python -m pydanticai_multiagent.services.johnpye_auth"
            )

        playwright = sync_playwright().start()
        # Use non-headless to avoid Cloudflare detection
        browser = playwright.chromium.launch(headless=headless)
        context = browser.new_context(storage_state=str(state_path))
        page = context.new_page()

        scraper = cls.__new__(cls)
        scraper.browser = browser
        scraper.context = context
        scraper.page = page
        scraper._playwright = playwright
        return scraper

    def _parse_price(self, text: str) -> int:
        """Parse price text to pence. E.g., '£20.09' -> 2009, '7.00' -> 700."""
        text = text.replace("£", "").replace("&#163;", "").strip()
        if not text:
            return 0
        try:
            return int(float(text) * 100)
        except ValueError:
            return 0

    def _parse_invoice_page(self, soup: BeautifulSoup) -> list[dict[str, Any]]:
        """Parse invoice list from a page of HTML.

        Returns:
            List of invoice dicts from this page.
        """
        invoices = []

        # Find all invoice rows
        for row in soup.select("div.list"):
            cols = row.select("div.col-sm-2, div.col-sm-3")
            if len(cols) < 4:
                continue

            date_text = cols[0].get_text(strip=True)
            invoice_link = cols[1].select_one("a")
            if not invoice_link:
                continue

            invoice_number = invoice_link.get_text(strip=True)
            invoice_url = invoice_link.get("href", "")

            site = cols[2].get_text(strip=True) if len(cols) > 2 else ""

            status_col = cols[3] if len(cols) > 3 else None
            status = "new"
            total_text = ""

            if status_col:
                label = status_col.select_one("span.label")
                if label:
                    status_text = label.get_text(strip=True).lower()
                    if "paid" in status_text:
                        status = "paid"
                    elif "new" in status_text:
                        status = "new"

                total_text = status_col.get_text(strip=True)
                price_match = re.search(r"£?([\d,.]+)", total_text)
                if price_match:
                    total_text = price_match.group(1)

            try:
                # UK date format: DD/MM/YYYY
                date = datetime.strptime(date_text.split()[0], "%d/%m/%Y")
            except ValueError:
                date = datetime.now()

            invoices.append(
                {
                    "invoice_number": invoice_number,
                    "date": date,
                    "site": site,
                    "status": status,
                    "total_pence": self._parse_price(total_text),
                    "url": invoice_url,
                }
            )

        return invoices

    def get_invoice_list(self) -> list[dict[str, Any]]:
        """Fetch list of all invoices, handling pagination.

        Returns:
            List of dicts with: invoice_number, date, site, status, total_pence, url
        """
        all_invoices = []
        # John Pye uses 0-indexed pagination: page 1 = no param, page 2 = ?page=1
        page_index = 0

        while True:
            if page_index == 0:
                url = f"{self.BASE_URL}/Account/Invoice/Purchases"
            else:
                url = f"{self.BASE_URL}/Account/Invoice/Purchases?page={page_index}"
            self.page.goto(url, timeout=60000)
            # Wait for invoice content to load
            self.page.wait_for_selector("div.list", timeout=30000)

            html = self.page.content()
            soup = BeautifulSoup(html, "html.parser")

            page_invoices = self._parse_invoice_page(soup)

            if not page_invoices:
                # No more invoices on this page
                break

            all_invoices.extend(page_invoices)

            # Check if there's a next page by looking for page links
            # John Pye shows links like: « 1 2 » where 2 links to ?page=1
            pagination = soup.select_one("ul.pagination")
            has_next = False
            if pagination:
                # Look for any link to the next page index
                for link in pagination.select("a"):
                    href = link.get("href", "")
                    if f"page={page_index + 1}" in href:
                        has_next = True
                        break

            if not has_next:
                break

            page_index += 1

            # Safety limit to prevent infinite loops
            if page_index > 50:
                break

        return all_invoices

    def get_invoice_detail(self, invoice_number: str) -> ScrapedInvoice | None:
        """Fetch detailed invoice data including lot items.

        Args:
            invoice_number: The invoice ID (e.g., '453742776')

        Returns:
            ScrapedInvoice with full details, or None if not found.
        """
        return_url = "/Account/Invoice/Purchases"
        url = f"{self.BASE_URL}/Account/Invoice/{invoice_number}?returnUrl={return_url}"
        self.page.goto(url, timeout=60000)
        # Wait for table with lot items
        self.page.wait_for_selector("table", timeout=30000)

        html = self.page.content()
        soup = BeautifulSoup(html, "html.parser")

        # Parse lot items from the items table (has "Lot" in header)
        lot_items = []
        # Find the table with Lot header among multiple tables
        table = None
        for t in soup.select("table.table"):
            headers = [th.get_text(strip=True) for th in t.select("th")]
            if "Lot" in headers:
                table = t
                break

        if table:
            for row in table.select("tr"):
                cells = row.select("td")
                if len(cells) >= 5:
                    lot_num = cells[0].get_text(strip=True)
                    if not lot_num or not lot_num.isdigit():
                        continue

                    desc_cell = cells[1]
                    desc_link = desc_cell.select_one("a")
                    if desc_link:
                        description = desc_link.get_text(strip=True)
                    else:
                        description = desc_cell.get_text(strip=True)
                    listing_url = desc_link.get("href") if desc_link else None

                    lot_items.append(
                        ScrapedLotItem(
                            lot_number=lot_num,
                            description=description,
                            listing_url=str(listing_url) if listing_url else None,
                            hammer_price=self._parse_price(cells[2].get_text(strip=True)),
                            vat=self._parse_price(cells[3].get_text(strip=True)),
                            subtotal=self._parse_price(cells[4].get_text(strip=True)),
                        )
                    )

        # Parse summary values
        net_hammer = 0
        delivery = 0
        buyers_premium = 0
        total_vat = 0
        grand_total = 0

        for row in soup.select("tr"):
            label = row.select_one("label")
            if label:
                label_text = label.get_text(strip=True).lower()
                value_cells = row.select("td")
                if len(value_cells) >= 2:
                    value = self._parse_price(value_cells[-2].get_text(strip=True))

                    if "net hammer" in label_text:
                        net_hammer = value
                    elif "delivery" in label_text:
                        delivery = value
                    elif "buyer" in label_text and "premium" in label_text:
                        buyers_premium = value
                    elif "total vat" in label_text:
                        total_vat = value
                    elif "subtotal" in label_text:
                        grand_total = value

        # Check payment status
        status = "new"
        if soup.select_one("img[src*='paid'], .paid, [class*='PAID']"):
            status = "paid"

        # Check delivery status
        delivery_status = None
        for step in ["Paid", "Preparing", "Packed", "Shipped", "Delivered"]:
            elem = soup.find(string=re.compile(step, re.I))
            if elem:
                parent = elem.find_parent(class_=re.compile("active|complete", re.I))
                if parent:
                    delivery_status = step.lower()

        return ScrapedInvoice(
            invoice_number=invoice_number,
            date=datetime.now(),
            site="JohnPye",
            status=status,
            delivery_status=delivery_status,
            total_pence=grand_total,
            lot_items=lot_items,
            net_hammer_pence=net_hammer,
            delivery_pence=delivery,
            buyers_premium_pence=buyers_premium,
            total_vat_pence=total_vat,
            grand_total_pence=grand_total,
        )

    def sync_all_invoices(self) -> list[ScrapedInvoice]:
        """Fetch all invoices with their full details.

        Returns:
            List of fully populated ScrapedInvoice objects.
        """
        invoice_list = self.get_invoice_list()
        detailed_invoices = []

        for inv in invoice_list:
            detail = self.get_invoice_detail(inv["invoice_number"])
            if detail:
                detail.date = inv["date"]
                detail.status = inv["status"]
                detailed_invoices.append(detail)

        return detailed_invoices

    def close(self) -> None:
        """Close browser and cleanup."""
        self.context.close()
        self.browser.close()
        if hasattr(self, "_playwright"):
            self._playwright.stop()

    def __enter__(self) -> "JohnPyeScraper":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()
