from enum import Enum
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Literal
from datetime import datetime


class MatchTier(str, Enum):
    CERTAIN = "CERTAIN"
    HIGH = "HIGH"
    MEDIUM = "MEDIUM"
    LIKELY = "LIKELY"
    POSSIBLE = "POSSIBLE"


class MatchType(str, Enum):
    exact_domain = "exact_domain"
    variant = "variant"


class DomainRelation(str, Enum):
    EXACT = "EXACT"
    FAMILY = "FAMILY"
    SUBDOMAIN = "SUBDOMAIN"
    NONE = "NONE"


class SuggestionStatus(str, Enum):
    pending = "pending"
    accepted = "accepted"
    rejected = "rejected"
    linked = "linked"


class FieldScoreDetail(BaseModel):
    # The API may send either *_field or *_column depending on source
    source_field: Optional[str] = None
    source_column: Optional[str] = None
    reference_field: Optional[str] = None
    reference_column: Optional[str] = None
    algorithm: Optional[str] = None
    weight: float = 0.0
    score: float = Field(
        0.0, ge=0.0, le=1.0, description="Score normalized to 0..1 range"
    )
    # New, optional: typed relationship on domain evidence
    relation: Optional[DomainRelation] = None
    family_id: Optional[str] = None


class ExplanationBullet(BaseModel):
    type: str = Field(
        ..., description="Algorithm type (e.g., 'exact', 'fuzzy', 'domain')"
    )
    field_pair: str = Field(..., description="Field mapping (e.g., 'email->email')")
    score: float = Field(
        ..., ge=0.0, le=1.0, description="Match score for this field pair"
    )


class L2ASuggestionItem(BaseModel):
    """L2A (Lead to Account) suggestion item"""

    id: str
    lead_id: str
    account_id: str
    score: float = Field(..., ge=0.0, le=1.0, description="Overall match score")
    tier: MatchTier
    match_type: MatchType
    status: SuggestionStatus
    created_at: Optional[datetime] = None

    # Evidence aggregates (always included)
    evidence_count: int = Field(..., description="Number of matching evidence pieces")
    evidence_strength: float = Field(
        ..., ge=0.0, le=1.0, description="Weighted evidence strength"
    )
    evidence_flags: List[str] = Field(
        default_factory=list, description="Evidence characteristics"
    )
    reasons_text: Optional[str] = Field(
        None, description="Human-readable match reasons"
    )

    # Optional detailed fields (when include=explanations or expand=true)
    field_score_details: Optional[List[FieldScoreDetail]] = Field(
        None, description="Detailed field-level scores"
    )
    explanations: Optional[List[ExplanationBullet]] = Field(
        None, description="Compact explanation bullets"
    )
    reasons: Optional[Dict[str, Any]] = Field(None, description="Legacy reasons dict")
    explanation: Optional[str] = Field(None, description="Legacy explanation text")

    # Hydrated display fields (when expand=true)
    lead_name: Optional[str] = None
    lead_company: Optional[str] = None
    lead_email: Optional[str] = None
    account_name: Optional[str] = None
    account_website: Optional[str] = None


class DedupeSuggestionItem(BaseModel):
    """Dedupe suggestion item for duplicate detection"""

    id: str
    object_type: Literal["Lead", "Contact", "Account"]
    left_id: str = Field(..., description="First record ID")
    right_id: str = Field(..., description="Second record ID")
    score: float = Field(..., ge=0.0, le=1.0, description="Overall match score")
    tier: MatchTier
    match_type: MatchType
    status: SuggestionStatus
    created_at: Optional[datetime] = None

    # Evidence aggregates (always included)
    evidence_count: int = Field(..., description="Number of matching evidence pieces")
    evidence_strength: float = Field(
        ..., ge=0.0, le=1.0, description="Weighted evidence strength"
    )
    evidence_flags: List[str] = Field(
        default_factory=list, description="Evidence characteristics"
    )
    ensemble_used: bool = Field(
        False, description="Whether multiple algorithms were used"
    )

    # Optional detailed fields (when include=explanations)
    field_score_details: Optional[List[FieldScoreDetail]] = Field(
        None, description="Detailed field-level scores"
    )
    explanations: Optional[List[ExplanationBullet]] = Field(
        None, description="Compact explanation bullets"
    )
    reasons: Optional[Dict[str, Any]] = Field(
        None, description="Field-level match reasons"
    )
    explanation: Optional[str] = Field(None, description="Legacy explanation text")


class L2ASuggestionsResponse(BaseModel):
    """Response for L2A suggestions list endpoint"""

    items: List[L2ASuggestionItem]
    total: int = Field(..., description="Total number of suggestions")
    page: int = Field(..., description="Current page number")
    page_size: int = Field(..., description="Items per page")


class DedupeSuggestionsResponse(BaseModel):
    """Response for Dedupe suggestions list endpoint"""

    items: List[DedupeSuggestionItem]
    total: int = Field(..., description="Total number of suggestions")
    page: int = Field(..., description="Current page number")
    page_size: int = Field(..., description="Items per page")


class ExplainResponse(BaseModel):
    """Detailed explanation for a single match"""

    id: str
    tier: MatchTier
    match_type: MatchType
    match_score: float = Field(..., ge=0.0, le=1.0, description="Overall match score")
    evidence_count: int = Field(..., description="Number of evidence pieces")
    evidence_strength: float = Field(
        ..., ge=0.0, le=1.0, description="Weighted evidence strength"
    )
    evidence_flags: List[str] = Field(
        default_factory=list, description="Evidence characteristics"
    )
    ensemble_used: bool = Field(
        False, description="Whether multiple algorithms were used"
    )
    field_score_details: List[FieldScoreDetail] = Field(
        ..., description="Detailed field-level scores"
    )
    kind: Optional[Literal["l2a", "dedupe"]] = None
    version: str = "v1"
    has_exact_anchor: Optional[bool] = None


class MergeCandidate(BaseModel):
    """A record candidate in a merge cluster"""

    id: str
    label: str = Field(..., description="Display label for the record")
    tier: MatchTier
    score: float = Field(..., ge=0.0, le=1.0)


class FieldValue(BaseModel):
    """Field value with conflict detection"""

    field: str
    label: str
    group: str = Field(..., description="Field group (Identity, Contact, etc.)")
    hasConflict: bool = Field(..., description="Whether values differ across records")
    values: Dict[str, Any] = Field(..., description="Record ID to value mapping")
    locked: bool = Field(False, description="Whether field is system-locked")


class HierarchyContext(BaseModel):
    """Account hierarchy context for merge decisions"""

    relationship: Literal["none", "parent", "subsidiary", "sibling"] = "none"
    parent_id: Optional[str] = None
    ultimate_parent_id: Optional[str] = None


class MergePlanResponse(BaseModel):
    """Comprehensive merge plan with guardrails"""

    cluster_id: str
    object_type: Literal["Lead", "Contact", "Account"]
    primary_id: Optional[str] = Field(None, description="Suggested primary record")
    policy_version: str = Field("default", description="Applied policy version")
    anchor: str = Field(
        ..., description="Anchor type (email, domain, phone, external_id, unknown)"
    )
    candidates: List[MergeCandidate]
    fields: List[FieldValue]
    conflict_count: int = Field(..., description="Number of fields with conflicts")
    risk_flags: List[str] = Field(default_factory=list, description="Risk indicators")
    hierarchy_context: HierarchyContext
    cluster_guardrail: Optional[Literal["weak_anchor"]] = Field(
        None, description="Guardrail type if any"
    )
    cluster_guardrail_reason: Optional[str] = Field(
        None, description="Human-readable guardrail reason"
    )
