"""Validates a Public Portal self-registration (POST /auth/register)
against the tenant's registration config: the extra fields in
registration_field_schema (app/schemas/identity.py::RegistrationFieldDef)
and the built-in fields' settings in RegistrationSettings — required-ness
plus each field's validation rules/messages (the same rule engine Forms use,
app/core/form_validation.py). Mirrors the checks Register.tsx makes in the
browser; this is the authoritative copy.
"""

from datetime import date
from typing import Any

from app.core.form_validation import validate_field_values
from app.schemas.identity import BuiltinRegistrationField, RegistrationFieldDef, RegistrationSettings

# The Register page's own labels for its built-in fields.
BUILTIN_LABELS = {"organization_name": "Organization name", "name": "Your name", "phone": "Phone number"}


def _required_error(label: str, required_message: str | None) -> ValueError:
    if required_message and required_message.strip():
        return ValueError(required_message.strip().replace("{label}", label))
    return ValueError(f"'{label}' is required")


def _check_rules(key: str, label: str, rules: list, value: Any) -> None:
    if not rules:
        return
    field = {"name": key, "label": label, "type": "text", "validation_rules": [r.model_dump(mode="json") for r in rules]}
    errors = validate_field_values([field], {key: value})
    if key in errors:
        raise ValueError(f"{label}: {errors[key]}")


def validate_builtin_registration_fields(settings: RegistrationSettings, values: dict[str, Any]) -> None:
    """Raises ValueError (message names the field) on the first built-in
    field that's missing when required, or fails one of its rules."""
    for key, label in BUILTIN_LABELS.items():
        field: BuiltinRegistrationField = getattr(settings, key)
        if field.hidden:
            continue
        value = values.get(key)
        if value is None or str(value).strip() == "":
            if field.required:
                raise _required_error(label, field.required_message)
            continue
        _check_rules(key, label, field.validation_rules, str(value).strip())


def validate_registration_field_values(
    schema: list[RegistrationFieldDef], values: dict[str, Any]
) -> dict[str, Any]:
    """Returns a cleaned dict containing only keys declared in `schema`,
    coerced/checked per field. Raises ValueError with a message naming the
    offending field on the first failure — routers/auth.py turns that into a
    400."""
    cleaned: dict[str, Any] = {}
    for field in schema:
        value = values.get(field.key)
        if value is None or value == "":
            if field.required:
                raise _required_error(field.label, field.required_message)
            continue

        if field.type == "text":
            if not isinstance(value, str):
                raise ValueError(f"'{field.label}' must be text")
            cleaned[field.key] = value

        elif field.type == "number":
            if isinstance(value, bool) or not isinstance(value, (int, float)):
                raise ValueError(f"'{field.label}' must be a number")
            cleaned[field.key] = value

        elif field.type == "boolean":
            if not isinstance(value, bool):
                raise ValueError(f"'{field.label}' must be true or false")
            cleaned[field.key] = value

        elif field.type == "date":
            if not isinstance(value, str):
                raise ValueError(f"'{field.label}' must be an ISO date (YYYY-MM-DD)")
            try:
                date.fromisoformat(value[:10])
            except ValueError:
                raise ValueError(f"'{field.label}' must be an ISO date (YYYY-MM-DD)") from None
            cleaned[field.key] = value

        elif field.type == "single_select":
            if value not in (field.options or []):
                raise ValueError(f"'{field.label}' must be one of: {', '.join(field.options or [])}")
            cleaned[field.key] = value

        _check_rules(field.key, field.label, field.validation_rules, value)

    return cleaned
