"""Persisted, digest-bound reconciliation state for onboarding answers."""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import Literal

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

from checkin_cli.nutrition_onboarding_contract import (
    QUESTION_FIELDS,
    canonical_digest,
)


def _require_digest(value: str, *, field: str) -> str:
    if len(value) != 64 or any(character not in "0123456789abcdef" for character in value):
        raise ValueError(f"{field} must be a lowercase SHA-256 digest")
    return value


class PersistedClarification(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)

    field: str
    kind: Literal["ambiguity", "contradiction"]
    question_ko: str = Field(min_length=1, max_length=180)

    @field_validator("field")
    @classmethod
    def require_canonical_field(cls, value: str) -> str:
        if value not in QUESTION_FIELDS:
            raise ValueError("clarification field is not canonical")
        return value

    @field_validator("question_ko")
    @classmethod
    def require_plain_question(cls, value: str) -> str:
        question = value.strip()
        if question != value or not any("가" <= character <= "힣" for character in question):
            raise ValueError("clarification question must be bounded Korean text")
        return question


class PersistedReconciliation(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)

    schema_version: Literal["nutrition_input_reconciliation_v1"]
    state: Literal["clarifying", "resolved"]
    answers_digest: str
    advisory: dict[str, object]
    clarifications: tuple[PersistedClarification, ...]
    current_index: int = Field(ge=0, le=3)
    digest: str

    @field_validator("answers_digest", "digest")
    @classmethod
    def require_digest(cls, value: str, info: object) -> str:
        field_name = getattr(info, "field_name", "digest")
        return _require_digest(value, field=str(field_name))

    @model_validator(mode="after")
    def validate_state(self) -> PersistedReconciliation:
        if len(self.clarifications) > 3:
            raise ValueError("at most three clarifications are allowed")
        fields = [item.field for item in self.clarifications]
        if len(fields) != len(set(fields)):
            raise ValueError("clarification fields must be unique")
        if self.current_index > len(self.clarifications):
            raise ValueError("clarification cursor is invalid")
        expected_state = (
            "resolved"
            if self.current_index == len(self.clarifications)
            else "clarifying"
        )
        if self.state != expected_state:
            raise ValueError("reconciliation state does not match its cursor")
        payload = self.model_dump(mode="json", exclude={"digest"})
        if canonical_digest(payload) != self.digest:
            raise ValueError("reconciliation digest is stale")
        return self


def build_reconciliation(
    *,
    answers_digest: str,
    advisory: Mapping[str, object],
    clarifications: Sequence[Mapping[str, object]],
) -> dict[str, object]:
    _require_digest(answers_digest, field="answers_digest")
    parsed = tuple(
        PersistedClarification.model_validate(dict(item))
        for item in clarifications
    )
    payload: dict[str, object] = {
        "schema_version": "nutrition_input_reconciliation_v1",
        "state": "resolved" if not parsed else "clarifying",
        "answers_digest": answers_digest,
        "advisory": dict(advisory),
        "clarifications": [
            item.model_dump(mode="json") for item in parsed
        ],
        "current_index": 0,
    }
    payload["digest"] = canonical_digest(payload)
    return PersistedReconciliation.model_validate(payload).model_dump(mode="json")


def load_reconciliation(value: object) -> dict[str, object]:
    return PersistedReconciliation.model_validate(value).model_dump(mode="json")


def advance_reconciliation(
    value: object,
    *,
    field: str,
    answers_digest: str,
) -> dict[str, object]:
    record = PersistedReconciliation.model_validate(value)
    if record.state != "clarifying":
        raise ValueError("reconciliation is already resolved")
    current = record.clarifications[record.current_index]
    if current.field != field:
        raise ValueError("field is not the current clarification")
    _require_digest(answers_digest, field="answers_digest")
    next_index = record.current_index + 1
    payload = record.model_dump(mode="json", exclude={"digest"})
    payload["answers_digest"] = answers_digest
    payload["current_index"] = next_index
    payload["state"] = (
        "resolved"
        if next_index == len(record.clarifications)
        else "clarifying"
    )
    payload["digest"] = canonical_digest(payload)
    return PersistedReconciliation.model_validate(payload).model_dump(mode="json")


def require_resolved_reconciliation(
    value: object,
    *,
    answers_digest: str,
) -> dict[str, object]:
    record = PersistedReconciliation.model_validate(value)
    if record.state != "resolved":
        raise ValueError("input reconciliation is not resolved")
    if record.answers_digest != answers_digest:
        raise ValueError("input reconciliation answers digest is stale")
    return record.model_dump(mode="json")
