"""Pure Form submission validation. No persistence, invoke, or anti-abuse.

Depends only on `canonical` types. Same definition + payload always yields the
same result. Adapters (`/api/contact`, `submitFormEntry`) are compatibility
fixtures, not the source of truth.
"""
from __future__ import annotations

import math
import re
from collections.abc import Mapping
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, StrictStr

from src.domain.forms.canonical import (
    FILE_RUNTIME_SUPPORT,
    STRING_FIELD_TYPES,
    FormDefinition,
    FormField,
    FormSubmission,
    FormValue,
    parse_form_file_ref,
)

InputState = Literal["missing", "null", "blank", "empty_list", "value"]

ERROR_CODES: tuple[str, ...] = (
    "required",
    "invalid_type",
    "invalid_email",
    "invalid_phone",
    "invalid_number",
    "invalid_date",
    "invalid_option",
    "unsupported_field_type",
    "unknown_field",
)

UNKNOWN_FIELD_POLICY = "reject"
REQUIRED_CHECKBOX_MEANS = "boolean_present"
DEFAULT_SERVER_INJECTION = False
TEXT_TRIM = True
EMAIL_CASE = "preserve"
PHONE_RULE = "trimmed_charset_length_not_e164"
NUMBER_STRING_COERCION = "complete_numeric_literal_only"
BOOLEAN_STRING_COERCION = False
ERROR_AGGREGATION = "all_fields"
CROSS_FIELD_VALIDATION = "deferred"

_EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
_PHONE_RE = re.compile(r"^[+0-9() \-]{1,32}$")
_INT_RE = re.compile(r"[+-]?\d+")
_DEC_RE = re.compile(r"[+-]?(?:\d+\.\d*|\.\d+)")
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")

_ERROR_MESSAGES: dict[str, str] = {
    "required": "This field is required.",
    "invalid_type": "This value has the wrong type.",
    "invalid_email": "Enter a valid email address.",
    "invalid_phone": "Enter a valid phone number.",
    "invalid_number": "Enter a valid number.",
    "invalid_date": "Enter a date as YYYY-MM-DD.",
    "invalid_option": "Choose a listed option.",
    "unsupported_field_type": "This field type cannot be submitted yet.",
    "unknown_field": "This field is not part of the form.",
}

_OMIT = object()

FORMS_VALIDATION_SEMANTICS: dict[str, Any] = {
    "slice_id": "7c_form_validation_semantics",
    "status": "frozen",
    "unknown_field_policy": UNKNOWN_FIELD_POLICY,
    "required_checkbox_means": REQUIRED_CHECKBOX_MEANS,
    "default_server_injection": DEFAULT_SERVER_INJECTION,
    "text_trim": TEXT_TRIM,
    "email_case": EMAIL_CASE,
    "phone_rule": PHONE_RULE,
    "number_string_coercion": NUMBER_STRING_COERCION,
    "boolean_string_coercion": BOOLEAN_STRING_COERCION,
    "error_aggregation": ERROR_AGGREGATION,
    "cross_field_validation": CROSS_FIELD_VALIDATION,
    "server_side_authoritative": True,
    "file_runtime_support": FILE_RUNTIME_SUPPORT,
    "anti_abuse": "deferred_7g",
    "persistence": False,
    "post_submit": False,
    "docs": "docs/audits/FORMS_VALIDATION_7C.md",
}


class FormConfigurationError(ValueError):
    """Malformed definition usage (not a user typing error)."""

    def __init__(self, code: str) -> None:
        self.code = code
        super().__init__(code)


class FormFieldError(BaseModel):
    model_config = ConfigDict(extra="forbid")
    field: StrictStr
    code: StrictStr
    message: StrictStr


class FormValidationResult(BaseModel):
    """Normalized canonical values, or field errors. Never persistence."""

    model_config = ConfigDict(extra="forbid")
    ok: bool
    form_key: StrictStr
    values: dict[str, Any] = Field(default_factory=dict)
    errors: list[FormFieldError] = Field(default_factory=list)

    def submission(self) -> FormSubmission:
        if not self.ok:
            raise FormConfigurationError("submission_not_valid")
        return FormSubmission(form_key=self.form_key, values=self.values)


def classify_input(raw: Mapping[str, Any], key: str) -> InputState:
    if key not in raw:
        return "missing"
    value = raw[key]
    if value is None:
        return "null"
    if isinstance(value, str) and value.strip() == "":
        return "blank"
    if isinstance(value, list) and len(value) == 0:
        return "empty_list"
    return "value"


def _is_number(value: Any) -> bool:
    return (
        isinstance(value, (int, float))
        and not isinstance(value, bool)
        and math.isfinite(value)
    )


def _parse_number(value: Any) -> tuple[Any, str | None]:
    if _is_number(value):
        if isinstance(value, float) and value.is_integer():
            return int(value), None
        return value, None
    if isinstance(value, str):
        text = value.strip()
        if text.lower() in {
            "nan",
            "inf",
            "+inf",
            "-inf",
            "infinity",
            "+infinity",
            "-infinity",
        }:
            return _OMIT, "invalid_number"
        if _INT_RE.fullmatch(text):
            return int(text), None
        if _DEC_RE.fullmatch(text):
            parsed = float(text)
            if not math.isfinite(parsed):
                return _OMIT, "invalid_number"
            return parsed, None
        return _OMIT, "invalid_number"
    return _OMIT, "invalid_type"


def _is_empty_state(field: FormField, state: InputState) -> bool:
    if state in {"missing", "null"}:
        return True
    if state == "blank":
        return field.type in STRING_FIELD_TYPES
    return field.type == "multi_select" and state == "empty_list"


def _field_error(field: str, code: str) -> FormFieldError:
    return FormFieldError(field=field, code=code, message=_ERROR_MESSAGES[code])


def _validate_text_like(field: FormField, value: Any) -> tuple[Any, str | None]:
    if not isinstance(value, str):
        return _OMIT, "invalid_type"
    text = value.strip() if TEXT_TRIM else value
    if text == "":
        return _OMIT, "blank"
    if field.type == "email":
        if not _EMAIL_RE.fullmatch(text):
            return _OMIT, "invalid_email"
        return text, None
    if field.type == "phone":
        if not _PHONE_RE.fullmatch(text) or not any(ch.isdigit() for ch in text):
            return _OMIT, "invalid_phone"
        return text, None
    if field.type == "date":
        if not _DATE_RE.fullmatch(text):
            return _OMIT, "invalid_date"
        return text, None
    if field.type in {"select", "radio"}:
        allowed = {option.value for option in field.options or []}
        if text not in allowed:
            return _OMIT, "invalid_option"
        return text, None
    return text, None


def _validate_multi_select(field: FormField, value: Any) -> tuple[Any, str | None]:
    if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
        return _OMIT, "invalid_type"
    allowed = {option.value for option in field.options or []}
    trimmed = [item.strip() if TEXT_TRIM else item for item in value]
    if any(item == "" or item not in allowed for item in trimmed):
        return _OMIT, "invalid_option"
    if len(trimmed) != len(set(trimmed)):
        return _OMIT, "invalid_option"
    return trimmed, None


def _validate_file(value: Any) -> tuple[Any, str | None]:
    parsed = parse_form_file_ref(value)
    if parsed is None:
        return _OMIT, "invalid_type"
    return parsed, None


def normalize_present_field_value(field: FormField, value: Any) -> tuple[Any, str | None]:
    """7C type-normalization for a present value. No requiredness. No omit-as-missing."""
    if field.type == "file":
        return _validate_file(value)
    if field.type == "checkbox":
        if not isinstance(value, bool):
            return _OMIT, "invalid_type"
        return value, None
    if field.type == "number":
        return _parse_number(value)
    if field.type == "multi_select":
        return _validate_multi_select(field, value)
    parsed, err = _validate_text_like(field, value)
    if err == "blank":
        return _OMIT, "blank"
    return parsed, err


def _validate_field(field: FormField, raw: Mapping[str, Any]) -> tuple[Any, str | None]:
    state = classify_input(raw, field.key)
    if field.type == "file":
        if _is_empty_state(field, state):
            return (_OMIT, "required") if field.required else (_OMIT, None)
        if state == "empty_list":
            return _OMIT, "invalid_type"
        return _validate_file(raw[field.key])
    if _is_empty_state(field, state):
        return (_OMIT, "required") if field.required else (_OMIT, None)
    if state == "empty_list" and field.type != "multi_select":
        return _OMIT, "invalid_type"
    parsed, err = normalize_present_field_value(field, raw[field.key])
    if err == "blank":
        return (_OMIT, "required") if field.required else (_OMIT, None)
    return parsed, err


def validate_form_values(
    definition: FormDefinition,
    raw: Mapping[str, Any],
    *,
    form_key: str | None = None,
) -> FormValidationResult:
    """Validate raw submitted values against a FormDefinition.

    Does not persist, send email, upsert CRM, or inspect anti-abuse tokens.
    """
    if form_key is not None and form_key != definition.key:
        raise FormConfigurationError("form_key_mismatch")
    if not isinstance(raw, Mapping) or isinstance(raw, (str, bytes)):
        raise FormConfigurationError("invalid_payload")

    declared = {field.key for field in definition.fields}
    errors: list[FormFieldError] = []
    values: dict[str, FormValue] = {}

    for key in sorted(k for k in raw if k not in declared):
        errors.append(_field_error(str(key), "unknown_field"))

    for field in definition.fields:
        parsed, code = _validate_field(field, raw)
        if code:
            errors.append(_field_error(field.key, code))
        elif parsed is not _OMIT:
            values[field.key] = parsed

    return FormValidationResult(
        ok=not errors,
        form_key=definition.key,
        values=values if not errors else {},
        errors=errors,
    )
