#!/usr/bin/env python3
"""Batch validation for email identity extraction.

Safe for LLM runs: prints progress, truncates long bodies, applies a per-message
process timeout, and writes JSONL incrementally so partial results are kept even
if a provider call stalls.
"""
from __future__ import annotations

import argparse
import csv
import json
import multiprocessing as mp
import os
import re
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List

from sqlalchemy import text

from app.commercial_service import normalize_fiscal_name
from app.db import engine


LEGAL_SUFFIX_TOKENS = {
    "lda", "limitada", "unipessoal", "sa", "s", "a", "sociedade",
    "mediação", "mediacao", "seguro", "seguros", "importação", "importacao",
    "exportação", "exportacao", "fabricação", "fabricacao", "representação",
    "representacao", "soluções", "solucoes", "metálicas", "metalicas",
}


def clean(value: Any) -> str:
    return re.sub(r"\s+", " ", str(value or "")).strip()


def norm(value: Any) -> str:
    return normalize_fiscal_name(value or "") or clean(value).casefold()


def meaningful_tokens(value: Any) -> set[str]:
    n = norm(value)
    tokens = {t for t in re.split(r"[^a-z0-9áàâãéèêíìîóòôõúùûç]+", n) if len(t) >= 3}
    return {t for t in tokens if t not in LEGAL_SUFFIX_TOKENS}


def mentions_match_fiscal(mentions: List[str], fiscal_name: str | None) -> bool:
    if not mentions or not fiscal_name:
        return False

    nf = norm(fiscal_name)
    fiscal_tokens = meaningful_tokens(fiscal_name)

    for mention in mentions:
        nm = norm(mention)
        if not nm:
            continue
        if nm == nf:
            return True
        if len(nm) >= 4 and (nm in nf or nf in nm):
            return True
        mention_tokens = meaningful_tokens(mention)
        if not mention_tokens or not fiscal_tokens:
            continue
        overlap = mention_tokens & fiscal_tokens
        if len(overlap) >= 2:
            return True
        if len(mention_tokens) <= 2 and overlap:
            return True
    return False


def classify(identity: Dict[str, Any], fiscal_customer: str | None) -> str:
    if identity.get("_error"):
        return "EXTRACTION_ERROR"
    if identity.get("_timeout"):
        return "EXTRACTION_TIMEOUT"

    mentions = identity.get("company_mentions") or []
    domain = identity.get("domain") or ""
    person = identity.get("person_name") or ""

    if mentions and fiscal_customer:
        if mentions_match_fiscal(mentions, fiscal_customer):
            return "OK_MENTION_COMPATIBLE_WITH_FISCAL"
        return "CONFLICT_MENTION_DIFFERS_FROM_FISCAL"
    if mentions and not fiscal_customer:
        return "OK_MENTION_AVAILABLE_NO_FISCAL"
    if not mentions and fiscal_customer:
        return "WEAK_NO_COMPANY_MENTION_HAS_FISCAL"
    if domain and person:
        return "WEAK_PERSON_AND_DOMAIN_ONLY"
    if domain:
        return "WEAK_DOMAIN_ONLY"
    return "NO_USEFUL_IDENTITY"


def fetch_cases(limit: int, offset: int) -> List[Dict[str, Any]]:
    with engine.begin() as conn:
        rows = conn.execute(text("""
            WITH ranked AS (
                SELECT
                    o.id::text AS opportunity_id,
                    o.title AS opportunity_title,
                    o.customer_name,
                    o.customer_email,
                    c.name AS fiscal_customer,
                    c.tax_id AS fiscal_tax_id,
                    c.email AS fiscal_email,
                    t.id::text AS task_id,
                    t.action_code,
                    t.route,
                    t.created_at AS task_created_at,
                    m.id::text AS message_id,
                    COALESCE(NULLIF(m.clean_body, ''), NULLIF(m.raw_body, '')) AS body,
                    COALESCE(
                        NULLIF(m.metadata->>'subject', ''),
                        NULLIF(re.payload->'conversation'->'additional_attributes'->>'mail_subject', ''),
                        NULLIF(re.payload->'content_attributes'->'email'->>'subject', ''),
                        NULLIF(re.payload->'conversation'->'messages'->0->'content_attributes'->'email'->>'subject', '')
                    ) AS subject,
                    COALESCE(
                        NULLIF(re.payload->'sender'->>'email', ''),
                        NULLIF(re.payload->'conversation'->'meta'->'sender'->>'email', ''),
                        NULLIF(re.payload->'conversation'->'contact_inbox'->>'source_id', ''),
                        NULLIF(o.customer_email, '')
                    ) AS sender_email,
                    ROW_NUMBER() OVER (
                        PARTITION BY o.id
                        ORDER BY t.created_at DESC
                    ) AS rn
                FROM opportunities o
                JOIN tasks t ON t.opportunity_id = o.id
                LEFT JOIN messages m ON m.id = t.message_id
                LEFT JOIN raw_events re ON re.id = t.raw_event_id
                LEFT JOIN customers c ON c.id = o.local_customer_id
                WHERE COALESCE(NULLIF(m.clean_body, ''), NULLIF(m.raw_body, '')) IS NOT NULL
            )
            SELECT *
            FROM ranked
            WHERE rn = 1
            ORDER BY task_created_at DESC
            LIMIT :limit
            OFFSET :offset
        """), {"limit": limit, "offset": offset}).mappings().all()
    return [dict(r) for r in rows]


def _domain_from_email(email: str) -> str:
    if "@" not in (email or ""):
        return ""
    return email.rsplit("@", 1)[1].lower().strip()


def worker_extract(queue: mp.Queue, body: str, email: str, subject: str, use_llm: bool) -> None:
    try:
        from app.email_identity_extraction_service import extract_email_identity

        identity = extract_email_identity(
            body or "",
            email=email or "",
            subject=subject or "",
            use_llm=use_llm,
        )
        queue.put({"ok": True, "identity": identity})
    except Exception as exc:  # noqa: BLE001 validation tool should keep going
        queue.put({"ok": False, "error": f"{type(exc).__name__}: {exc}"})


def extract_with_timeout(body: str, email: str, subject: str, use_llm: bool, timeout_seconds: int) -> Dict[str, Any]:
    if not use_llm:
        from app.email_identity_extraction_service import extract_email_identity
        return extract_email_identity(body or "", email=email or "", subject=subject or "", use_llm=False)

    queue: mp.Queue = mp.Queue()
    proc = mp.Process(target=worker_extract, args=(queue, body, email, subject, use_llm))
    proc.start()
    proc.join(timeout_seconds)

    if proc.is_alive():
        proc.terminate()
        proc.join(5)
        return {
            "_timeout": True,
            "method": "timeout",
            "confidence": 0,
            "email": email,
            "domain": _domain_from_email(email),
            "person_name": "",
            "company_mentions": [],
            "address": "",
            "phones": [],
            "websites": [],
            "evidence": [f"timeout após {timeout_seconds}s"],
        }

    if queue.empty():
        return {
            "_error": True,
            "method": "error",
            "confidence": 0,
            "email": email,
            "domain": _domain_from_email(email),
            "person_name": "",
            "company_mentions": [],
            "address": "",
            "phones": [],
            "websites": [],
            "evidence": ["processo terminou sem resultado"],
        }

    result = queue.get()
    if result.get("ok"):
        return result["identity"]

    return {
        "_error": True,
        "method": "error",
        "confidence": 0,
        "email": email,
        "domain": _domain_from_email(email),
        "person_name": "",
        "company_mentions": [],
        "address": "",
        "phones": [],
        "websites": [],
        "evidence": [result.get("error")],
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--limit", type=int, default=20)
    parser.add_argument("--offset", type=int, default=0)
    parser.add_argument("--use-llm", action="store_true")
    parser.add_argument("--model", default="", help="Override EMAIL_IDENTITY_LLM_MODEL for this validation run")
    parser.add_argument("--fallback-model", default="", help="Override EMAIL_IDENTITY_LLM_FALLBACK_MODEL for this validation run")
    parser.add_argument("--timeout-seconds", type=int, default=30)
    parser.add_argument("--max-body-chars", type=int, default=3500)
    parser.add_argument("--out-dir", default="reports")
    args = parser.parse_args()
    if args.model:
        os.environ["EMAIL_IDENTITY_LLM_MODEL"] = args.model
    if args.fallback_model:
        os.environ["EMAIL_IDENTITY_LLM_FALLBACK_MODEL"] = args.fallback_model

    cases = fetch_cases(args.limit, args.offset)
    out_dir = Path(args.out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)

    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    mode = "llm" if args.use_llm else "regex"
    jsonl_path = out_dir / f"email_identity_validation_{mode}_{stamp}.jsonl"
    csv_path = out_dir / f"email_identity_validation_{mode}_{stamp}.csv"

    results: List[Dict[str, Any]] = []
    counts: Dict[str, int] = {}

    model_label = os.getenv("EMAIL_IDENTITY_LLM_MODEL") or os.getenv("OPENROUTER_MODEL", "")
    fallback_label = os.getenv("EMAIL_IDENTITY_LLM_FALLBACK_MODEL", "")
    print(
        f"Casos: {len(cases)} | mode={mode} | offset={args.offset} | "
        f"timeout={args.timeout_seconds}s | max_body_chars={args.max_body_chars} | "
        f"model={model_label if args.use_llm else '-'} | fallback={fallback_label if args.use_llm and fallback_label else '-'}",
        flush=True,
    )
    print(f"JSONL incremental: {jsonl_path}", flush=True)

    with jsonl_path.open("w", encoding="utf-8") as jf:
        for idx, row in enumerate(cases, start=1):
            email = row.get("sender_email") or row.get("customer_email") or ""
            body = (row.get("body") or "")[: args.max_body_chars]
            subject = row.get("subject") or ""

            print(f"\n[{idx}/{len(cases)}] {row.get('opportunity_title')} | {email}", flush=True)

            identity = extract_with_timeout(
                body=body,
                email=email,
                subject=subject,
                use_llm=args.use_llm,
                timeout_seconds=args.timeout_seconds,
            )
            status = classify(identity, row.get("fiscal_customer"))

            result = {
                "status": status,
                "opportunity_id": row.get("opportunity_id"),
                "opportunity_title": row.get("opportunity_title"),
                "task_id": row.get("task_id"),
                "action_code": row.get("action_code"),
                "sender_email": email,
                "customer_name": row.get("customer_name"),
                "customer_email": row.get("customer_email"),
                "fiscal_customer": row.get("fiscal_customer"),
                "fiscal_tax_id": row.get("fiscal_tax_id"),
                "fiscal_email": row.get("fiscal_email"),
                "person_name": identity.get("person_name"),
                "company_mentions": identity.get("company_mentions") or [],
                "domain": identity.get("domain"),
                "address": identity.get("address"),
                "phones": identity.get("phones") or [],
                "confidence": identity.get("confidence"),
                "method": identity.get("method"),
                "llm_model": identity.get("llm_model"),
                "fallback_used": identity.get("fallback_used"),
                "evidence": identity.get("evidence") or [],
            }
            results.append(result)
            counts[status] = counts.get(status, 0) + 1
            jf.write(json.dumps(result, ensure_ascii=False, default=str) + "\n")
            jf.flush()

            print(
                "status:", status,
                "| person:", result["person_name"],
                "| companies:", result["company_mentions"],
                flush=True,
            )

    csv_fields = [
        "status", "opportunity_id", "opportunity_title", "task_id", "action_code",
        "sender_email", "customer_name", "customer_email", "fiscal_customer",
        "fiscal_tax_id", "fiscal_email", "person_name", "company_mentions",
        "domain", "address", "phones", "confidence", "method", "llm_model", "fallback_used", "evidence",
    ]
    with csv_path.open("w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=csv_fields)
        writer.writeheader()
        for r in results:
            csv_row = dict(r)
            for key in ["company_mentions", "phones", "evidence"]:
                csv_row[key] = " | ".join(str(x) for x in csv_row.get(key) or [])
            writer.writerow(csv_row)

    print("\nResumo:")
    for status, count in sorted(counts.items()):
        print(f"{status}: {count}")

    print("\nFicheiros gerados:")
    print(jsonl_path)
    print(csv_path)


if __name__ == "__main__":
    main()
