"""
Validation hooks — internal sanity checks. NOT an externally-presentable
audit.

The load-bearing check is `camelot_vs_llm_count`: Camelot's detected
table count per page versus the LLM's image-based count. Mismatches
surface explicitly — financial-document liability framing means we
**do not silently judge** which extractor is right. The user's standing
instruction: "If this was a financial document and the critical items
happened to be on the failed page the legal liability falls on us. We
have no defense."
"""

from __future__ import annotations

from collections import Counter
from pathlib import Path
from typing import Dict, List, Optional

from pydantic import BaseModel, Field

from quber.agents.llm_client import LLMClient
from quber.core.extractors.base import ExtractedTable


class CountMismatch(BaseModel):
    page: int
    camelot_count: int
    llm_count: int

    @property
    def delta(self) -> int:
        return self.llm_count - self.camelot_count


class ValidationReport(BaseModel):
    source: str
    total_pages: int
    pages_checked: int
    mismatches: List[CountMismatch] = Field(default_factory=list)
    errors: List[str] = Field(default_factory=list)

    @property
    def has_mismatches(self) -> bool:
        return len(self.mismatches) > 0


def camelot_counts_by_page(tables: List[ExtractedTable]) -> Dict[int, int]:
    return dict(Counter(t.page for t in tables))


async def camelot_vs_llm_count(
    source: Path,
    tables: List[ExtractedTable],
    page_images: List[Path],
    llm: LLMClient,
    pages: Optional[List[int]] = None,
    max_concurrent: int = 5,
) -> ValidationReport:
    """Per-page LLM count calls run concurrently (capped by `max_concurrent`).

    Each page's count is independent, so they parallelize cleanly.
    """
    import asyncio

    camelot_counts = camelot_counts_by_page(tables)
    total_pages = len(page_images)
    pages_to_check = pages or list(range(1, total_pages + 1))
    report = ValidationReport(source=str(source), total_pages=total_pages, pages_checked=len(pages_to_check))

    valid_pages = [p for p in pages_to_check if 1 <= p <= total_pages]
    for p in pages_to_check:
        if not (1 <= p <= total_pages):
            report.errors.append(f"page {p} out of range (1..{total_pages})")

    semaphore = asyncio.Semaphore(max_concurrent)

    async def count_one(page: int):
        image_path = page_images[page - 1]
        async with semaphore:
            try:
                return page, await llm.count_tables(image_path), None
            except Exception as exc:
                return page, None, exc

    results = await asyncio.gather(*(count_one(p) for p in valid_pages))
    for page, llm_count, exc in results:
        if exc is not None:
            report.errors.append(f"page {page}: llm.count_tables failed: {exc}")
            continue
        assert llm_count is not None
        camelot_count = camelot_counts.get(page, 0)
        if camelot_count != llm_count:
            report.mismatches.append(
                CountMismatch(page=page, camelot_count=camelot_count, llm_count=llm_count)
            )

    return report


def camelot_vs_llm_count_sync(
    source: Path,
    tables: List[ExtractedTable],
    page_images: List[Path],
    llm: LLMClient,
    pages: Optional[List[int]] = None,
    max_concurrent: int = 5,
) -> ValidationReport:
    """Sync entry point — wraps the async version with `asyncio.run`."""
    import asyncio

    return asyncio.run(camelot_vs_llm_count(source, tables, page_images, llm, pages, max_concurrent))
