from __future__ import annotations

from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from .deps import require_api_key


router = APIRouter(prefix="/api/v1", tags=["PublicAPI"])


class TabularPreview(BaseModel):
    headers: List[str]
    rows: List[List[Any]]


class SuggestReq(BaseModel):
    profile: str = "balanced"
    source: TabularPreview
    reference: TabularPreview


class MappingOut(BaseModel):
    source: str
    reference: str
    algorithm: str
    weight: float


class SuggestResp(BaseModel):
    mappings: List[MappingOut]
    threshold: float
    blocking: Dict[str, Any]
    coverage: Optional[Dict[str, float]] = None
    estimated_pair_reduction: Optional[float] = None
    warnings: List[str] = []
    b2c_ratio: Optional[float] = None


def _rows_from_preview(pv: TabularPreview) -> List[Dict[str, Any]]:
    hdr = [str(h or "").strip() for h in pv.headers]
    out: List[Dict[str, Any]] = []
    for row in pv.rows:
        out.append(
            {hdr[i]: (row[i] if i < len(row) else None) for i in range(len(hdr))}
        )
    return out


def _ratio_nonblank(rows: List[Dict[str, Any]], col: str) -> float:
    if not rows:
        return 0.0
    return sum(1 for r in rows if str(r.get(col) or "").strip()) / len(rows)


def _unique_count(rows: List[Dict[str, Any]], col: str) -> int:
    return len(
        {
            str(r.get(col) or "").strip().lower()
            for r in rows
            if str(r.get(col) or "").strip()
        }
    )


B2C_TOP = frozenset(
    {
        "gmail.com",
        "yahoo.com",
        "hotmail.com",
        "outlook.com",
        "live.com",
        "aol.com",
        "icloud.com",
        "me.com",
        "msn.com",
        "comcast.net",
        "verizon.net",
        "att.net",
        "sbcglobal.net",
        "ymail.com",
        "proton.me",
        "protonmail.com",
        "zoho.com",
        "gmx.com",
        "mail.com",
        "yandex.ru",
        "qq.com",
        "163.com",
        "126.com",
        "sina.com",
        "sohu.com",
        "naver.com",
        "daum.net",
        "orange.fr",
        "wanadoo.fr",
        "free.fr",
        "laposte.net",
        "web.de",
        "t-online.de",
        "btinternet.com",
        "sky.com",
        "virginmedia.com",
        "blueyonder.co.uk",
        "tiscali.co.uk",
        "seznam.cz",
        "wp.pl",
        "o2.pl",
        "shaw.ca",
        "rogers.com",
        "telus.net",
        "bigpond.com",
        "optusnet.com.au",
        "bellsouth.net",
    }
)


def _root_domain_str(v: str) -> str:
    v = str(v or "").strip().lower()
    if not v:
        return ""
    if "@" in v:
        v = v.split("@", 1)[1]
    if v.startswith("http://") or v.startswith("https://"):
        v = v.split("://", 1)[1]
    if v.startswith("www."):
        v = v[4:]
    v = v.split("/", 1)[0]
    parts = v.split(".")
    return ".".join(parts[-2:]) if len(parts) >= 2 else v


def _b2c_ratio(rows: List[Dict[str, Any]]) -> float:
    total = b2c = 0
    for r in rows:
        dom = (
            r.get("Domain")
            or r.get("Website")
            or r.get("EmailDomain")
            or r.get("Email")
        )
        if not dom:
            continue
        rd = _root_domain_str(dom)
        if not rd:
            continue
        total += 1
        if rd in B2C_TOP:
            b2c += 1
    return (b2c / total) if total else 0.0


@router.post("/mappings/suggest", response_model=SuggestResp)
def suggest(req: SuggestReq, api_ctx=Depends(require_api_key)):
    """
    Lightweight mapping suggestion for Sheets preview.
    Uses simple heuristics; replace with your auto-configure when available.
    """
    src_rows = _rows_from_preview(req.source)
    ref_rows = _rows_from_preview(req.reference)

    # Heuristic mappings
    hdrs_src = {str(h).strip().lower(): h for h in req.source.headers}
    hdrs_ref = {str(h).strip().lower(): h for h in req.reference.headers}

    def has(hs, name):
        return name in hs

    mappings: List[MappingOut] = []
    if has(hdrs_src, "domain") and has(hdrs_ref, "domain"):
        mappings.append(
            MappingOut(
                source=hdrs_src["domain"],
                reference=hdrs_ref["domain"],
                algorithm="Domain",
                weight=1.0,
            )
        )
    if has(hdrs_src, "company") and (
        has(hdrs_ref, "name") or has(hdrs_ref, "account name")
    ):
        ref_name = hdrs_ref.get("name", hdrs_ref.get("account name"))
        mappings.append(
            MappingOut(
                source=hdrs_src["company"],
                reference=ref_name,
                algorithm="WRatio",
                weight=0.6,
            )
        )
    if not mappings and req.source.headers and req.reference.headers:
        mappings.append(
            MappingOut(
                source=req.source.headers[0],
                reference=req.reference.headers[0],
                algorithm="WRatio",
                weight=1.0,
            )
        )

    # Blocking candidates & threshold defaults
    # Normalize candidates to a canonical set (case-insensitive behavior downstream)
    candidates_raw = ["Domain", "EmailDomain", "Company"]
    blocking = {
        "enabled": True,
        "candidates": candidates_raw,
        "suggested": "Domain",
        "key_length": 3,
    }
    threshold = 0.65

    # Coverage + estimated pair reduction on sample
    cand_cols = blocking.get("candidates", ["Domain", "EmailDomain", "Company"])
    coverage = {
        c: max(_ratio_nonblank(src_rows, c), _ratio_nonblank(ref_rows, c))
        for c in cand_cols
    }
    ns, nr = max(len(src_rows), 1), max(len(ref_rows), 1)
    try:
        totals = sum(_unique_count(ref_rows, c) for c in cand_cols) or 1
        epr = max(0.0, min(0.99, 1.0 - (totals / ((ns * nr) ** 0.5))))
    except Exception:
        epr = 0.0

    # B2C ratio + warnings and smarter blocker suggestion
    b2c_ratio = max(_b2c_ratio(src_rows), _b2c_ratio(ref_rows))
    warnings: List[str] = []
    if coverage.get("Domain", 0.0) < 0.25 and coverage.get("Company", 0.0) < 0.25:
        warnings.append(
            "Low coverage for Domain and Company; consider turning Blocking off"
        )
    if b2c_ratio > 0.25:
        warnings.append(f"{int(b2c_ratio*100)}% consumer email domains detected")
        # Case-insensitive check for 'Company'
        cand_lower = [c.lower() for c in blocking.get("candidates", [])]
        if "company" in cand_lower:
            # Preserve original casing by mapping back
            idx = cand_lower.index("company")
            blocking["suggested"] = blocking["candidates"][idx]

    payload = {
        "mappings": mappings,
        "threshold": threshold,
        "blocking": blocking,
        "coverage": coverage,
        "estimated_pair_reduction": epr,
        "warnings": warnings,
        "b2c_ratio": b2c_ratio,
    }
    res = JSONResponse(content=payload)
    res.headers["Cache-Control"] = "no-store"
    return res
