"""Lead-to-account match health check."""

from __future__ import annotations

import json
import os
import time
from datetime import datetime
from typing import Any, Dict, List, Tuple

from .base import BaseScoutCheck, PreflightResult
from ..crm_provider import Filter, FilterExpr, FilterOp
from ..models import EvidenceRow, RecommendedAction, ScoutFinding


class LeadToAccountMatchHealthCheck(BaseScoutCheck):
    _PERSONAL_DOMAINS = {
        "gmail.com",
        "yahoo.com",
        "hotmail.com",
        "outlook.com",
        "icloud.com",
        "aol.com",
        "protonmail.com",
    }

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._domain_field = str(self.params.get("domain_field") or "Website")

    def preflight(self) -> PreflightResult:
        base = super().preflight()
        if base.unsupported:
            return base
        try:
            self.provider.describe("lead")
        except Exception as exc:
            return PreflightResult(ok=False, unsupported=True, reason=str(exc))
        return base

    def execute(self) -> ScoutFinding:
        start = time.perf_counter()
        try:
            preflight = self.preflight()
            if preflight.unsupported:
                return self._unsupported_finding(preflight.reason or "Unsupported")

            lookback_days = int(self.params.get("lookback_days", 30) or 30)
            threshold = float(self.params.get("match_rate_threshold", 0.7) or 0.7)
            include_converted = bool(self.params.get("include_converted", False))

            total_count = self._count_total(lookback_days)
            matched_count = self._count_matched(lookback_days)
            match_rate = (matched_count / total_count) if total_count else 0.0

            summary_fields = {
                "match_rate": match_rate,
                "matched_count": matched_count,
                "total_count": total_count,
                "threshold": threshold,
            }
            summary_severity = self.definition.severity_default if match_rate < threshold else "info"
            evidence: List[EvidenceRow] = [
                EvidenceRow(
                    record_id="summary",
                    sobject="Lead",
                    fields=summary_fields,
                    reason="Lead-to-account match rate",
                    reason_code="MATCH_RATE",
                    severity=summary_severity,
                )
            ]

            evidence.extend(self._sample_unmatched(lookback_days, include_converted))

            recommendation_pack_path = None
            if match_rate < threshold:
                diagnosis_sample_size = int(self.params.get("diagnosis_sample_size", 1000) or 1000)
                unmatched_rows = self._sample_unmatched_rows(
                    lookback_days,
                    include_converted,
                    diagnosis_sample_size,
                )
                diagnosis = self._diagnose_match_failures(unmatched_rows, diagnosis_sample_size)
                summary_fields.update(
                    {
                        "missing_domain_pct": self._category_pct(diagnosis, "missing_website"),
                        "personal_email_pct": self._category_pct(diagnosis, "personal_email"),
                        "normalization_issue_pct": self._category_pct(diagnosis, "normalization"),
                    }
                )
                output_dir = self.params.get("recommendation_output_dir")
                try:
                    recommendation_pack_path = self._write_recommendation_pack(
                        diagnosis,
                        match_rate,
                        threshold,
                        output_dir=output_dir,
                    )
                    summary_fields["recommendation_pack_path"] = recommendation_pack_path
                except Exception as exc:
                    self._warnings.append(f"Failed to write recommendation pack: {exc}")

            status = "findings" if match_rate < threshold else "ok"
            duration_ms = int((time.perf_counter() - start) * 1000)
            return ScoutFinding(
                scout_name=self.definition.name,
                display_name=self.definition.display_name,
                description=self.definition.description,
                category=self.definition.category,
                severity=self.definition.severity_default,
                total_records_scanned=total_count,
                findings_count=len(evidence) - 1,
                evidence=evidence,
                recommended_actions=self._recommended_actions(
                    match_rate,
                    threshold,
                    recommendation_pack_path,
                ),
                soql_queries=self._soql_queries,
                duration_ms=duration_ms,
                parameters_used=self.params,
                status=status,
                warnings=self._warnings,
            )
        except Exception as exc:
            duration_ms = int((time.perf_counter() - start) * 1000)
            return self._error_finding(str(exc), duration_ms=duration_ms)

    def _count_total(self, lookback_days: int) -> int:
        filters: List[FilterExpr] = [Filter("CreatedDate", FilterOp.LAST_N_DAYS, lookback_days)]
        count = self.provider.count("lead", filters)
        self._collect_provider_meta()
        return count

    def _count_matched(self, lookback_days: int) -> int:
        filters: List[FilterExpr] = [
            Filter("CreatedDate", FilterOp.LAST_N_DAYS, lookback_days),
            Filter("ConvertedAccountId", FilterOp.IS_NOT_NULL),
        ]
        count = self.provider.count("lead", filters)
        self._collect_provider_meta()
        return count

    def _sample_unmatched(self, lookback_days: int, include_converted: bool) -> List[EvidenceRow]:
        filters: List[FilterExpr] = [
            Filter("CreatedDate", FilterOp.LAST_N_DAYS, lookback_days),
            Filter("ConvertedAccountId", FilterOp.IS_NULL),
        ]
        if not include_converted:
            filters.append(Filter("IsConverted", FilterOp.EQ, False))

        fields = self._unmatched_fields()

        rows = self.provider.query(
            "lead",
            fields,
            filters,
            order_by="CreatedDate",
            order_dir="DESC",
            limit=self.top_n,
        )
        self._collect_provider_meta()
        evidence: List[EvidenceRow] = []
        for row in rows:
            record_id = row.get("Id", "")
            if not record_id:
                continue
            fields_payload = {k: row.get(k) for k in fields if k in row}
            if self._domain_field:
                fields_payload["Domain"] = row.get(self._domain_field)
            evidence.append(
                EvidenceRow(
                    record_id=record_id,
                    sobject="Lead",
                    fields=fields_payload,
                    reason="Unmatched lead",
                    reason_code="UNMATCHED_LEAD",
                    severity=self.definition.severity_default,
                )
            )
        return evidence

    def _sample_unmatched_rows(
        self,
        lookback_days: int,
        include_converted: bool,
        sample_size: int,
    ) -> List[Dict[str, Any]]:
        if sample_size <= 0:
            return []
        filters: List[FilterExpr] = [
            Filter("CreatedDate", FilterOp.LAST_N_DAYS, lookback_days),
            Filter("ConvertedAccountId", FilterOp.IS_NULL),
        ]
        if not include_converted:
            filters.append(Filter("IsConverted", FilterOp.EQ, False))

        fields = self._unmatched_fields()
        rows = self.provider.query(
            "lead",
            fields,
            filters,
            order_by="CreatedDate",
            order_dir="DESC",
            limit=sample_size,
        )
        self._collect_provider_meta()
        return rows

    def _unmatched_fields(self) -> List[str]:
        fields = [
            "Id",
            "Company",
            "Email",
            "Website",
            "LeadSource",
            "Status",
        ]
        if self._domain_field and self._domain_field not in fields:
            fields.append(self._domain_field)
        return fields

    def _diagnose_match_failures(
        self,
        unmatched_leads: List[Dict[str, Any]],
        sample_size: int = 1000,
    ) -> Dict[str, Any]:
        sample = unmatched_leads[: max(sample_size, 0)] if sample_size else []
        total = len(sample)
        missing_website = 0
        personal_email = 0
        normalization_issues = 0

        for lead in sample:
            website_value = lead.get(self._domain_field) or lead.get("Website") or ""
            website = str(website_value).strip() if website_value is not None else ""
            email_value = lead.get("Email") or ""
            email = str(email_value).strip() if email_value is not None else ""

            if not website:
                missing_website += 1
            if email and "@" in email:
                domain = email.split("@", 1)[1].lower()
                if domain in self._PERSONAL_DOMAINS:
                    personal_email += 1
            if website:
                lower = website.lower()
                if (
                    lower.startswith("http://")
                    or lower.startswith("https://")
                    or lower.startswith("www.")
                    or "/" in lower
                ):
                    normalization_issues += 1

        def _pct(count: int) -> float:
            return (count / total) if total else 0.0

        failure_categories = [
            {"reason": "missing_website", "count": missing_website, "pct": _pct(missing_website)},
            {"reason": "personal_email", "count": personal_email, "pct": _pct(personal_email)},
            {"reason": "normalization", "count": normalization_issues, "pct": _pct(normalization_issues)},
        ]
        failure_categories.sort(key=lambda item: item.get("count", 0), reverse=True)
        recommendations = self._generate_recommendations(failure_categories, total)

        return {
            "sample_size": total,
            "failure_categories": failure_categories,
            "recommendations": recommendations,
        }

    def _generate_recommendations(
        self,
        failure_categories: List[Dict[str, Any]],
        sample_size: int,
    ) -> List[Dict[str, Any]]:
        recommendations: List[Dict[str, Any]] = []
        for category in failure_categories:
            count = int(category.get("count", 0) or 0)
            if count <= 0 or sample_size <= 0:
                continue
            pct = float(category.get("pct", 0.0) or 0.0)
            priority = "high" if pct > 0.3 else "medium"
            rec: Dict[str, Any] = {
                "category": category.get("reason"),
                "priority": priority,
                "expected_lift": None,
                "actions": [],
            }
            if category.get("reason") == "missing_website":
                lift_center = max(int(pct * 40), 5)
                low = max(lift_center - 5, 0)
                high = lift_center + 5
                rec["expected_lift"] = f"+{low}-{high}% (heuristic estimate)"
                rec["actions"] = [
                    "Add custom field: Email_Domain__c (formula: RIGHT(Email, LEN(Email) - FIND('@', Email)))",
                    "Add custom field: Website_Domain__c (formula: normalize Website field)",
                    "Update matching rules to use domain fields instead of Company name only",
                    "Consider data enrichment for missing domains",
                ]
            elif category.get("reason") == "personal_email":
                lift_center = max(int(pct * 15), 3)
                low = max(lift_center - 5, 0)
                high = lift_center + 5
                rec["expected_lift"] = f"+{low}-{high}% (heuristic estimate)"
                rec["actions"] = [
                    "Add validation rule: require company email for Enterprise/Corporate leads",
                    "Create Flow: flag leads with personal emails for manual review",
                    "Update lead forms to require company email (not personal)",
                    "Allow personal emails for self-service or SMB channels only",
                ]
            elif category.get("reason") == "normalization":
                lift_center = max(int(pct * 20), 4)
                low = max(lift_center - 5, 0)
                high = lift_center + 5
                rec["expected_lift"] = f"+{low}-{high}% (heuristic estimate)"
                rec["actions"] = [
                    "Create Flow: normalize Website field on lead create/update",
                    "Strip 'www.', 'http://', 'https://' prefixes",
                    "Remove trailing slashes and paths",
                    "Consider Apex normalization for edge cases",
                ]
            recommendations.append(rec)
        return recommendations

    def _write_recommendation_pack(
        self,
        diagnosis: Dict[str, Any],
        match_rate: float,
        threshold: float,
        output_dir: str | None = None,
    ) -> str:
        resolved_dir = os.path.expanduser(output_dir or "~/.g_gremlin/artifacts")
        os.makedirs(resolved_dir, exist_ok=True)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        base_name = f"l2a_recommendation_pack_{timestamp}"
        json_path = os.path.join(resolved_dir, f"{base_name}.json")
        md_path = os.path.join(resolved_dir, f"{base_name}.md")

        pack = {
            "diagnosis": {
                "match_rate": match_rate,
                "threshold": threshold,
                "sample_size": diagnosis.get("sample_size", 0),
                "failure_categories": diagnosis.get("failure_categories", []),
            },
            "recommendations": diagnosis.get("recommendations", []),
            "generated_at": datetime.now().isoformat(),
            "disclaimer": "Expected lift estimates are heuristics, not guarantees. Actual results may vary.",
        }

        with open(json_path, "w", encoding="utf-8") as handle:
            json.dump(pack, handle, indent=2)

        markdown = self._format_recommendation_markdown(pack)
        with open(md_path, "w", encoding="utf-8") as handle:
            handle.write(markdown)

        return json_path

    def _format_recommendation_markdown(self, pack: Dict[str, Any]) -> str:
        diagnosis = pack.get("diagnosis", {})
        lines = [
            "# Lead-to-Account Match Health Recommendations",
            "",
            f"Generated: {pack.get('generated_at', '')}",
            f"Current Match Rate: {diagnosis.get('match_rate', 0):.1%}",
            f"Target Threshold: {diagnosis.get('threshold', 0):.1%}",
            f"Sample Size: {diagnosis.get('sample_size', 0)} unmatched leads",
            "",
            "## Failure Analysis",
            "",
        ]
        for cat in diagnosis.get("failure_categories", []):
            reason = str(cat.get("reason", "")).replace("_", " ").title()
            count = cat.get("count", 0)
            pct = cat.get("pct", 0.0)
            lines.append(f"- {reason}: {count} leads ({pct:.1%})")
        lines.extend(
            [
                "",
                "## Recommendations",
                "",
                f"Disclaimer: {pack.get('disclaimer', '')}",
                "",
            ]
        )
        for rec in pack.get("recommendations", []):
            category = str(rec.get("category", "")).replace("_", " ").title()
            priority = rec.get("priority", "")
            lines.append(f"### {category} (Priority: {priority})")
            expected_lift = rec.get("expected_lift")
            if expected_lift:
                lines.append(f"Expected Lift: {expected_lift}")
            lines.append("")
            lines.append("Actions:")
            for action in rec.get("actions", []):
                lines.append(f"- {action}")
            lines.append("")
        return "\n".join(lines).rstrip() + "\n"

    def _category_pct(self, diagnosis: Dict[str, Any], reason: str) -> float:
        for category in diagnosis.get("failure_categories", []):
            if category.get("reason") == reason:
                return float(category.get("pct", 0.0) or 0.0)
        return 0.0

    def _recommended_actions(
        self,
        match_rate: float,
        threshold: float,
        recommendation_pack_path: str | None = None,
    ) -> List[RecommendedAction]:
        actions: List[RecommendedAction] = []
        if match_rate < threshold:
            subject = f"L2A match rate below threshold ({match_rate:.0%})"
            details: Dict[str, Any] = {"subject": subject}
            owner_id = self.params.get("task_owner_id")
            if owner_id:
                details["owner_id"] = owner_id
            if recommendation_pack_path:
                details["description"] = f"See recommendation pack: {recommendation_pack_path}"
            actions.append(
                RecommendedAction(
                    action_type="create_task",
                    label="Review L2A match improvement recommendations",
                    risk_level="low",
                    details=details,
                )
            )
        actions.append(
            RecommendedAction(
                action_type="scout.lead_to_account_match_health",
                label="L2A match health audit",
                risk_level="safe",
            )
        )
        return actions

    def build_count_filters(self) -> Tuple[str, List[FilterExpr]]:
        return "lead", []

    def build_sample_request(self) -> Tuple[str, List[str], List[FilterExpr], str | None, str, bool]:
        return "lead", ["Id"], [], None, "ASC", False

    def evaluate(self, rows: List[Dict[str, Any]]) -> List[EvidenceRow]:
        return []


__all__ = ["LeadToAccountMatchHealthCheck"]
