"""Rule and rule-group schemas."""

from __future__ import annotations

import re
from datetime import datetime
from typing import Any

from pydantic import Field, model_validator

from app.models.enums import RuleAction, RuleMatchType, RuleOperator
from app.rules.fields import RULE_FIELDS, normalize_field
from app.schemas.base import CamelModel

VALUE_LESS_OPERATORS = {RuleOperator.IS_EMPTY, RuleOperator.IS_NOT_EMPTY}


class RuleBase(CamelModel):
    name: str = Field(min_length=1, max_length=200)
    description: str | None = None
    campaign_id: int | None = None
    group_id: int | None = None
    field: str
    operator: RuleOperator
    value: str | None = None
    value2: str | None = None
    case_sensitive: bool = False
    action: RuleAction = RuleAction.SCORE
    points: int = Field(default=0, ge=-100, le=100)
    action_value: str | None = None
    priority: int = 100
    is_active: bool = True

    @model_validator(mode="after")
    def _validate(self) -> "RuleBase":
        self.field = normalize_field(self.field)
        if self.field not in RULE_FIELDS:
            raise ValueError(
                f"unknown field '{self.field}'. Supported: {', '.join(sorted(RULE_FIELDS))}"
            )
        if self.operator not in VALUE_LESS_OPERATORS and not self.value:
            raise ValueError(f"operator '{self.operator}' requires a value")
        if self.operator == RuleOperator.BETWEEN and not self.value2:
            raise ValueError("operator 'between' requires both value and value2")
        if self.operator == RuleOperator.REGEX and self.value:
            # Reject an unparseable pattern here rather than letting it fail
            # silently at scoring time (a broken reject rule would fail open).
            try:
                re.compile(self.value)
            except re.error as exc:
                raise ValueError(f"invalid regular expression: {exc}") from exc
        if self.action == RuleAction.SCORE and self.points == 0:
            raise ValueError("a scoring rule needs a non-zero point value")
        if self.action in (RuleAction.SELECT_TEMPLATE, RuleAction.ADD_PHRASE, RuleAction.TAG):
            if not self.action_value:
                raise ValueError(f"action '{self.action}' requires an action value")
        return self


class RuleCreate(RuleBase):
    pass


class RuleUpdate(CamelModel):
    name: str | None = None
    description: str | None = None
    campaign_id: int | None = None
    group_id: int | None = None
    field: str | None = None
    operator: RuleOperator | None = None
    value: str | None = None
    value2: str | None = None
    case_sensitive: bool | None = None
    action: RuleAction | None = None
    points: int | None = None
    action_value: str | None = None
    priority: int | None = None
    is_active: bool | None = None


class RuleRead(RuleBase):
    id: int
    created_at: datetime
    updated_at: datetime


class RuleGroupBase(CamelModel):
    name: str = Field(min_length=1, max_length=160)
    description: str | None = None
    campaign_id: int | None = None
    match_type: RuleMatchType = RuleMatchType.ALL
    is_active: bool = True
    priority: int = 100


class RuleGroupCreate(RuleGroupBase):
    pass


class RuleGroupUpdate(CamelModel):
    name: str | None = None
    description: str | None = None
    match_type: RuleMatchType | None = None
    is_active: bool | None = None
    priority: int | None = None


class RuleGroupRead(RuleGroupBase):
    id: int
    rules: list[RuleRead] = Field(default_factory=list)


class RuleTestRequest(CamelModel):
    """Try a rule (saved or unsaved) against a sample prospect."""

    rule: RuleBase | None = None
    rule_id: int | None = None
    lead_id: int | None = None
    sample: dict[str, Any] = Field(default_factory=dict)


class RuleTestResponse(CamelModel):
    matched: bool
    points: int = 0
    action: str = "score"
    detail: str = ""
    error: str | None = None


class RuleMetadata(CamelModel):
    fields: list[str]
    operators: list[dict[str, str]]
    actions: list[str]
    score_bands: list[dict[str, Any]]
