"""Pydantic models for Salesforce Flow Builder."""

from __future__ import annotations

from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Union

from pydantic import BaseModel, Field, PrivateAttr, field_validator, model_validator


# === ENUMS ===


class FlowTriggerType(str, Enum):
    RECORD_BEFORE_SAVE = "RecordBeforeSave"  # v1 only
    RECORD_AFTER_SAVE = "RecordAfterSave"


class TriggerEvent(str, Enum):
    CREATE = "Create"
    UPDATE = "Update"


class ConditionOperator(str, Enum):
    EQUAL_TO = "EqualTo"
    NOT_EQUAL_TO = "NotEqualTo"
    CONTAINS = "Contains"
    STARTS_WITH = "StartsWith"
    ENDS_WITH = "EndsWith"
    IS_NULL = "IsNull"
    IS_NOT_NULL = "IsNotNull"
    GREATER_THAN = "GreaterThan"
    GREATER_OR_EQUAL = "GreaterOrEqual"
    LESS_THAN = "LessThan"
    LESS_OR_EQUAL = "LessOrEqual"


class SalesforceFieldType(str, Enum):
    STRING = "string"
    PICKLIST = "picklist"
    MULTIPICKLIST = "multipicklist"
    BOOLEAN = "boolean"
    DOUBLE = "double"
    CURRENCY = "currency"
    INTEGER = "int"
    DATETIME = "datetime"
    DATE = "date"
    REFERENCE = "reference"
    PERCENT = "percent"
    PHONE = "phone"
    EMAIL = "email"
    URL = "url"
    TEXTAREA = "textarea"
    ID = "id"


class ValidationSeverity(str, Enum):
    ERROR = "error"
    WARNING = "warning"
    INFO = "info"


class RiskScore(str, Enum):
    LOW = "Low"
    MEDIUM = "Medium"
    HIGH = "High"
    CRITICAL = "Critical"


# === SCOUT CONTEXT ===


class FieldMetadata(BaseModel):
    """Single field metadata from Salesforce describe."""

    name: str  # Canonical API name (case-sensitive)
    label: str
    type: SalesforceFieldType
    updateable: bool = True
    createable: bool = True
    nillable: bool = True
    length: Optional[int] = None
    precision: Optional[int] = None
    scale: Optional[int] = None
    reference_to: Optional[List[str]] = None
    picklist_values: Optional[List[str]] = None  # Global values (no record type)
    default_value: Optional[Any] = None
    is_formula: bool = False


class RecordTypeInfo(BaseModel):
    """Record type metadata."""

    id: str
    developer_name: str
    name: str  # Label
    is_default: bool = False


class ValidationRuleMetadata(BaseModel):
    """Existing validation rule on the object."""

    name: str
    active: bool
    error_condition_formula: str
    error_message: str
    referenced_fields: List[str] = Field(default_factory=list)


class ExistingFlowMetadata(BaseModel):
    """Existing flow on the object."""

    name: str
    api_name: str
    process_type: str
    status: str
    trigger_type: Optional[str] = None


class ScoutContext(BaseModel):
    """Complete metadata context for flow building."""

    object_api_name: str
    object_label: str

    # Field metadata - keyed by canonical API name
    fields: Dict[str, FieldMetadata] = Field(default_factory=dict)
    # Case-insensitive lookup: lowercase -> canonical name
    _fields_lower_map: Dict[str, str] = PrivateAttr(default_factory=dict)

    required_fields: List[str] = Field(default_factory=list)
    validation_rules: List[ValidationRuleMetadata] = Field(default_factory=list)
    existing_flows: List[ExistingFlowMetadata] = Field(default_factory=list)

    # Record types
    record_types: List[RecordTypeInfo] = Field(default_factory=list)

    # Picklist values BY record type: {field_api: {record_type_id: [values]}}
    picklist_restrictions: Dict[str, Dict[str, List[str]]] = Field(default_factory=dict)

    # FLS diagnostics for missing standard fields
    fls_warnings: List[str] = Field(default_factory=list)

    gathered_at: datetime = Field(default_factory=datetime.utcnow)

    @model_validator(mode="after")
    def build_lower_map(self) -> "ScoutContext":
        """Build case-insensitive lookup map."""
        self._fields_lower_map = {k.lower(): k for k in self.fields.keys()}
        return self

    def get_canonical_name(self, name: str) -> Optional[str]:
        """Get canonical field name from any case variant."""
        if name in self.fields:
            return name
        return self._fields_lower_map.get(name.lower())

    def field_exists(self, name: str) -> bool:
        """Check if field exists (case-sensitive check first, then insensitive)."""
        return self.get_canonical_name(name) is not None

    def get_field(self, name: str) -> Optional[FieldMetadata]:
        """Get field metadata by name (handles case mismatch)."""
        canonical = self.get_canonical_name(name)
        return self.fields.get(canonical) if canonical else None

    def get_picklist_values(
        self, field: str, record_type_id: Optional[str]
    ) -> Optional[List[str]]:
        """Get picklist values for field, optionally filtered by record type."""
        if field not in self.picklist_restrictions:
            # Fall back to global values
            f = self.get_field(field)
            return f.picklist_values if f else None

        if record_type_id and record_type_id in self.picklist_restrictions[field]:
            return self.picklist_restrictions[field][record_type_id]

        # No record type specified - return None to trigger blocking
        return None


# === FLOW TRIGGER (Typed Model) ===


class FlowTrigger(BaseModel):
    """Flow trigger configuration - TYPED, not dict."""

    object_api_name: str = Field(..., description="Must match ScoutContext.object_api_name")
    type: FlowTriggerType = FlowTriggerType.RECORD_BEFORE_SAVE
    events: List[TriggerEvent] = Field(..., min_length=1, max_length=2)
    record_type_id: Optional[str] = Field(None, description="Record type for picklist validation")

    @field_validator("type")
    @classmethod
    def validate_v1_trigger_type(cls, v: FlowTriggerType) -> FlowTriggerType:
        """v1: Allow before-save and after-save for supported fields."""
        if v not in (FlowTriggerType.RECORD_BEFORE_SAVE, FlowTriggerType.RECORD_AFTER_SAVE):
            raise ValueError(f"Unsupported trigger type {v}")
        return v

    @field_validator("events")
    @classmethod
    def validate_trigger_events(cls, v: List[TriggerEvent]) -> List[TriggerEvent]:
        if not v:
            raise ValueError("Trigger must include at least one event")
        # De-duplicate while preserving order
        seen = set()
        unique: List[TriggerEvent] = []
        for ev in v:
            if ev in seen:
                continue
            seen.add(ev)
            unique.append(ev)

        event_set = set(unique)
        if event_set == {TriggerEvent.CREATE, TriggerEvent.UPDATE}:
            return [TriggerEvent.CREATE, TriggerEvent.UPDATE]
        if event_set == {TriggerEvent.CREATE}:
            return [TriggerEvent.CREATE]
        if event_set == {TriggerEvent.UPDATE}:
            return [TriggerEvent.UPDATE]
        raise ValueError("Trigger events must be Create, Update, or both")


# === FLOW PLAN (AI Output) ===


class FlowCondition(BaseModel):
    """Single condition in entry criteria."""

    field: str
    operator: ConditionOperator
    value: Optional[Union[str, int, float, bool, List[str]]] = None
    value_is_field: bool = False

    @field_validator("field")
    @classmethod
    def validate_field_name(cls, v: str) -> str:
        if not v or not v[0].isalpha():
            raise ValueError("Field name must start with letter")
        return v


class FlowEntryCriteria(BaseModel):
    """Entry criteria with AND/OR logic."""

    logic: Literal["AND", "OR"] = "AND"
    conditions: List[FlowCondition] = Field(default_factory=list, max_length=10)

    def has_conditions(self) -> bool:
        """Check if there are actual conditions (not empty)."""
        return len(self.conditions) > 0


class FlowAssignmentValue(BaseModel):
    """Value for field assignment."""

    source: Literal["literal", "field_reference", "formula"]
    content: Optional[Union[str, int, float, bool]] = None


class FlowAction(BaseModel):
    """Single field assignment action."""

    type: Literal["assignment"] = "assignment"
    field: str
    value: FlowAssignmentValue


class FlowBranch(BaseModel):
    """Single decision branch with its own conditions and actions."""

    name: str = Field(..., min_length=1, max_length=80)
    logic: Literal["AND", "OR"] = "AND"
    conditions: List[FlowCondition] = Field(default_factory=list, max_length=10)
    actions: List[FlowAction] = Field(..., min_length=1, max_length=20)


class FlowSafetyCheck(BaseModel):
    """AI's self-assessment of risk."""

    risk_score: RiskScore
    reasoning: str


class FlowPlan(BaseModel):
    """Complete flow specification from Architect."""

    flow_name: str = Field(..., min_length=1, max_length=80)
    description: str
    trigger: FlowTrigger  # TYPED model, not dict
    entry_criteria: Optional[FlowEntryCriteria] = None
    actions: List[FlowAction] = Field(default_factory=list, max_length=20)
    branches: List[FlowBranch] = Field(default_factory=list, max_length=5)
    safety_check: Optional[FlowSafetyCheck] = None
    generated_at: datetime = Field(default_factory=datetime.utcnow)
    user_prompt: Optional[str] = None

    @field_validator("flow_name")
    @classmethod
    def validate_flow_name(cls, v: str) -> str:
        import re

        if not v.startswith("FO_"):
            v = f"FO_{v}"
        if not re.match(r"^FO_[A-Za-z][A-Za-z0-9_]*$", v):
            raise ValueError(f"Invalid flow name: {v}")
        return v

    @model_validator(mode="after")
    def check_actions_exist(self) -> "FlowPlan":
        has_actions = bool(self.actions)
        has_branches = bool(self.branches)
        if has_actions and has_branches:
            raise ValueError("FlowPlan cannot include both actions and branches")
        if not has_actions and not has_branches:
            raise ValueError("Flow must have at least one action or branch")
        return self

    def runs_on_update(self) -> bool:
        """Check if flow runs on updates."""
        return TriggerEvent.UPDATE in self.trigger.events

    def has_entry_criteria(self) -> bool:
        """Check if flow has meaningful entry criteria."""
        return self.entry_criteria is not None and self.entry_criteria.has_conditions()

    def iter_actions(self) -> List[FlowAction]:
        if self.branches:
            actions: List[FlowAction] = []
            for branch in self.branches:
                actions.extend(branch.actions)
            return actions
        return list(self.actions)

    def iter_conditions(self) -> List[FlowCondition]:
        conditions: List[FlowCondition] = []
        if self.entry_criteria and self.entry_criteria.conditions:
            conditions.extend(self.entry_criteria.conditions)
        if self.branches:
            for branch in self.branches:
                conditions.extend(branch.conditions)
        return conditions


# === VALIDATION RESULT (Sheriff Output) ===


class ValidationIssue(BaseModel):
    """Single validation issue."""

    severity: ValidationSeverity
    code: str
    message: str
    field: Optional[str] = None
    suggestion: Optional[str] = None
    branch_name: Optional[str] = None
    branch_index: Optional[int] = None


class ValidationResult(BaseModel):
    """Sheriff validation result."""

    valid: bool = False
    deployable: bool = False  # Explicit deploy gate
    issues: List[ValidationIssue] = Field(default_factory=list)
    error_count: int = 0
    warning_count: int = 0

    @model_validator(mode="after")
    def count_issues(self) -> "ValidationResult":
        self.error_count = sum(
            1 for i in self.issues if i.severity == ValidationSeverity.ERROR
        )
        self.warning_count = sum(
            1 for i in self.issues if i.severity == ValidationSeverity.WARNING
        )
        self.valid = self.error_count == 0
        self.deployable = self.valid  # Only deployable if no errors
        return self


# === CRITIC AUDIT (Advisory Only) ===


class FlowAuditRisk(BaseModel):
    severity: Literal["warning", "error"]
    category: Literal[
        "data_loss",
        "infinite_loop",
        "validation_conflict",
        "field_access",
        "logic_error",
        "unknown",
    ]
    message: str
    field: Optional[str] = None

    @field_validator("severity", mode="before")
    @classmethod
    def normalize_severity(cls, v: Any) -> str:
        if not isinstance(v, str):
            return "warning"
        low = v.strip().lower()
        return "error" if low == "error" else "warning"

    @field_validator("category", mode="before")
    @classmethod
    def normalize_category(cls, v: Any) -> str:
        if not isinstance(v, str):
            return "unknown"
        low = v.strip().lower()
        allowed = {
            "data_loss",
            "infinite_loop",
            "validation_conflict",
            "field_access",
            "logic_error",
            "unknown",
        }
        return low if low in allowed else "unknown"


class FlowAuditSuggestion(BaseModel):
    title: str
    description: str


class FlowAuditPatch(BaseModel):
    title: str
    description: str
    patch: Optional[str] = None


class FlowAudit(BaseModel):
    risk_score: RiskScore
    risks: List[FlowAuditRisk] = Field(default_factory=list)
    suggestions: List[FlowAuditSuggestion] = Field(default_factory=list)
    patches: List[FlowAuditPatch] = Field(default_factory=list)
    requires_acknowledgment: bool = False


# === API REQUEST/RESPONSE MODELS ===


class ScoutRequest(BaseModel):
    object_api_name: str
    include_validation_rules: bool = True
    include_existing_flows: bool = True


class ScoutResponse(BaseModel):
    success: bool
    context: Optional[ScoutContext] = None
    fls_warnings: List[str] = Field(default_factory=list)
    error: Optional[str] = None


class CoachMessage(BaseModel):
    role: Literal["user", "assistant"]
    content: str


class CoachQuestion(BaseModel):
    id: str
    text: str
    choices: List[str] = Field(default_factory=list)


class CoachAction(BaseModel):
    field: str
    value: Optional[Union[str, int, float, bool]] = None
    value_is_field: bool = False
    blank_only: bool = True


class CoachBranch(BaseModel):
    name: str = Field(..., min_length=1, max_length=80)
    logic: Literal["AND", "OR"] = "AND"
    conditions: List[FlowCondition] = Field(default_factory=list, max_length=10)
    actions: List[CoachAction] = Field(default_factory=list, max_length=20)


class CoachDraft(BaseModel):
    object_api_name: Optional[str] = None
    record_type_id: Optional[str] = None
    events: List[TriggerEvent] = Field(default_factory=list)
    logic: Literal["AND", "OR"] = "AND"
    conditions: List[FlowCondition] = Field(default_factory=list)
    actions: List[CoachAction] = Field(default_factory=list)
    branches: List[CoachBranch] = Field(default_factory=list, max_length=5)
    overwrite_policy: Optional[Literal["IF_BLANK", "OVERWRITE"]] = None

    @model_validator(mode="after")
    def check_branch_shape(self) -> "CoachDraft":
        has_actions = bool(self.actions)
        has_branches = bool(self.branches)
        if has_actions and has_branches:
            raise ValueError("CoachDraft cannot include both actions and branches")
        return self


class CoachRequest(BaseModel):
    message: str = Field(..., min_length=1, max_length=2000)
    object_api_name: Optional[str] = None
    record_type_id: Optional[str] = None
    draft: Optional[CoachDraft] = None
    history: List[CoachMessage] = Field(default_factory=list)
    context: Optional[ScoutContext] = None
    conversation_id: Optional[str] = None


class CoachResponse(BaseModel):
    success: bool
    needs_more_info: bool = True
    questions: List[CoachQuestion] = Field(default_factory=list)
    draft: Optional[CoachDraft] = None
    assistant_message: Optional[str] = None  # Conversational response from coach
    error: Optional[str] = None
    model_used: Optional[str] = None


class ArchitectRequest(BaseModel):
    prompt: str = Field(..., min_length=10, max_length=2000)
    object_api_name: str
    record_type_id: Optional[str] = None  # For picklist validation
    context: Optional[ScoutContext] = None
    job_id: Optional[str] = None


class ArchitectResponse(BaseModel):
    success: bool
    plan: Optional[FlowPlan] = None
    validation: Optional[ValidationResult] = None
    audit: Optional[FlowAudit] = None
    error: Optional[str] = None
    model_used: Optional[str] = None


class ValidateRequest(BaseModel):
    """Standalone validation request."""

    plan: FlowPlan
    context: ScoutContext


class ValidateResponse(BaseModel):
    """Validation result."""

    success: bool
    validation: ValidationResult
    deployable: bool


class CompileRequest(BaseModel):
    plan: FlowPlan
    context: ScoutContext


class CompileResponse(BaseModel):
    success: bool
    xml: Optional[str] = None
    flow_api_name: str
    error: Optional[str] = None


class DeployRequest(BaseModel):
    xml: str
    flow_api_name: str


class DeployResponse(BaseModel):
    success: bool
    deployment_id: Optional[str] = None
    flow_id: Optional[str] = None
    status: str = "pending"
    error: Optional[str] = None
    warnings: List[str] = Field(default_factory=list)


class DeployStatusRequest(BaseModel):
    deployment_id: str


class DeployStatusResponse(BaseModel):
    success: bool
    deployment_id: str
    done: bool
    status: str
    error: Optional[str] = None
    warnings: List[str] = Field(default_factory=list)
