"""Duplicate suspects check."""

from __future__ import annotations

import hashlib
from typing import Any, Dict, List

from .base import BaseScoutCheck
from ..crm_provider import Filter, FilterExpr, FilterOp
from ..domain_utils import extract_registrable_domain, is_free_domain
from ..models import EvidenceRow, RecommendedAction, ScoutFinding
from ..utils import parse_sf_datetime


def _normalize_name(value: str | None) -> str:
    if not value:
        return ""
    return " ".join(str(value).lower().split())


def _cluster_id(record_ids: List[str]) -> str:
    joined = ":".join(sorted([r for r in record_ids if r]))
    digest = hashlib.sha256(joined.encode("utf-8")).hexdigest()
    return digest[:12]


def _confidence_rank(value: str) -> int:
    mapping = {"low": 0, "medium": 1, "high": 2}
    return mapping.get(value.lower(), 1)


def _to_ts(value: Any) -> float:
    dt = parse_sf_datetime(str(value)) if value is not None else None
    if not dt:
        return 0.0
    return dt.timestamp()


def _to_created_ts(value: Any) -> float:
    dt = parse_sf_datetime(str(value)) if value is not None else None
    if not dt:
        return float("inf")
    return dt.timestamp()


def _completeness_score(row: Dict[str, Any]) -> int:
    fields = [row.get("Name"), row.get("Company"), row.get("Email"), row.get("Website")]
    return sum(1 for v in fields if v)


def _pick_survivor(cluster: List[Dict[str, Any]]) -> str | None:
    best_id = None
    best_key = None
    for row in cluster:
        record_id = row.get("Id")
        if not record_id:
            continue
        score = _completeness_score(row)
        last_activity = _to_ts(row.get("LastActivityDate"))
        created = _to_created_ts(row.get("CreatedDate"))
        key = (score, last_activity, -created)
        if best_key is None or key > best_key:
            best_key = key
            best_id = record_id
    return best_id


class DupeSuspectsCheck(BaseScoutCheck):
    def build_count_filters(self) -> tuple[str, List[FilterExpr]]:
        return "account", []

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

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

    def execute(self) -> ScoutFinding:
        import time

        started = time.perf_counter()
        evidence: List[EvidenceRow] = []
        total_scanned = 0

        check_accounts = bool(self.params.get("check_accounts", True))
        check_leads = bool(self.params.get("check_leads", True))
        min_cluster = int(self.params.get("min_cluster_size", 2))
        query_limit = int(self.params.get("query_limit", 2000))
        confidence_threshold = str(self.params.get("confidence_threshold", "medium")).lower()
        threshold_rank = _confidence_rank(confidence_threshold)

        records: List[Dict[str, Any]] = []

        if check_accounts:
            account_rows = self.provider.query(
                "account",
                ["Id", "Name", "Website", "LastActivityDate", "CreatedDate"],
                [Filter("Website", FilterOp.IS_NOT_NULL)],
                limit=query_limit,
            )
            self._collect_provider_meta()
            if len(account_rows) >= query_limit:
                self._warnings.append(
                    f"Account query capped at {query_limit} rows; results may be truncated."
                )
            total_scanned += len(account_rows)
            for row in account_rows:
                row["__sobject"] = "account"
            records.extend(account_rows)

        if check_leads:
            lead_rows = self.provider.query(
                "lead",
                ["Id", "Name", "Company", "Email", "LastActivityDate", "CreatedDate"],
                [
                    Filter("IsConverted", FilterOp.EQ, False),
                    Filter("Email", FilterOp.IS_NOT_NULL),
                ],
                limit=query_limit,
            )
            self._collect_provider_meta()
            if len(lead_rows) >= query_limit:
                self._warnings.append(
                    f"Lead query capped at {query_limit} rows; results may be truncated."
                )
            total_scanned += len(lead_rows)
            for row in lead_rows:
                row["__sobject"] = "lead"
            records.extend(lead_rows)

        domain_clusters: Dict[str, List[Dict[str, Any]]] = {}
        for row in records:
            sobject = row.get("__sobject", "")
            raw = row.get("Website") if sobject == "account" else row.get("Email")
            domain = extract_registrable_domain(raw)
            if not domain or is_free_domain(domain):
                continue
            domain_clusters.setdefault(domain, []).append(row)

        for domain, cluster in domain_clusters.items():
            if len(cluster) < min_cluster:
                continue

            related_ids = [r.get("Id", "") for r in cluster if r.get("Id")]
            cluster_id = _cluster_id(related_ids)
            survivor_id = _pick_survivor(cluster)

            name_map: Dict[str, List[Dict[str, Any]]] = {}
            for row in cluster:
                name = row.get("Name") or row.get("Company")
                key = _normalize_name(name)
                if not key:
                    continue
                name_map.setdefault(key, []).append(row)

            # Determine confidence per row
            for row in cluster:
                record_id = row.get("Id", "")
                if not record_id:
                    continue

                name = row.get("Name") or row.get("Company")
                key = _normalize_name(name)
                name_group = name_map.get(key, []) if key else []
                if len(name_group) >= 2:
                    confidence = "high"
                elif len(cluster) <= 3:
                    confidence = "medium"
                else:
                    confidence = "low"

                if _confidence_rank(confidence) < threshold_rank:
                    continue

                base_fields = {
                    "Name": row.get("Name"),
                    "Company": row.get("Company"),
                    "Email": row.get("Email"),
                    "Website": row.get("Website"),
                    "domain": domain,
                    "cluster_size": len(cluster),
                    "cluster_id": cluster_id,
                    "confidence": confidence,
                    "is_recommended_survivor": record_id == survivor_id,
                }

                evidence.append(
                    EvidenceRow(
                        record_id=record_id,
                        sobject=row.get("__sobject", ""),
                        fields=dict(base_fields),
                        reason=f"Domain appears in {len(cluster)} records",
                        reason_code="DOMAIN_COLLISION",
                        severity=self.definition.severity_default,
                        related_ids=[i for i in related_ids if i and i != record_id],
                    )
                )

                if len(name_group) >= 2:
                    evidence.append(
                        EvidenceRow(
                            record_id=record_id,
                            sobject=row.get("__sobject", ""),
                            fields=dict(base_fields),
                            reason="Matching name within domain cluster",
                            reason_code="NAME_COLLISION",
                            severity=self.definition.severity_default,
                            related_ids=[i for i in related_ids if i and i != record_id],
                        )
                    )

                if confidence == "high":
                    evidence.append(
                        EvidenceRow(
                            record_id=record_id,
                            sobject=row.get("__sobject", ""),
                            fields=dict(base_fields),
                            reason="High-confidence merge candidate",
                            reason_code="MERGE_CANDIDATE",
                            severity=self.definition.severity_default,
                            related_ids=[i for i in related_ids if i and i != record_id],
                        )
                    )

        total_findings = len(evidence)
        if len(evidence) > self.top_n:
            evidence = evidence[: self.top_n]

        status = "findings" if total_findings > 0 else "ok"
        duration_ms = int((time.perf_counter() - started) * 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_scanned,
            findings_count=total_findings,
            evidence=evidence,
            recommended_actions=self.get_recommended_actions(),
            soql_queries=self._soql_queries,
            duration_ms=duration_ms,
            parameters_used=self.params,
            status=status,
            warnings=self._warnings,
        )

    def get_recommended_actions(self) -> List[RecommendedAction]:
        return [
            RecommendedAction(
                action_type="create_cluster_tasks",
                label="Create dedupe review tasks per cluster",
                risk_level="low",
            ),
            RecommendedAction(
                action_type="flag_cluster_records",
                label="Flag records for dedupe review",
                risk_level="low",
            ),
            RecommendedAction(
                action_type="export_merge_worksheet",
                label="Export merge worksheet",
                risk_level="safe",
            ),
            RecommendedAction(
                action_type="manual_review",
                label="Manual merge review (no auto-merge)",
                risk_level="low",
            ),
        ]


__all__ = ["DupeSuspectsCheck"]
