"""Strict JSON/hash codec for weekly generation ledger rows."""

from __future__ import annotations

import hashlib
import json
from typing import ClassVar, Final, TypeAlias

from pydantic import BaseModel, ConfigDict, Field, ValidationError

from checkin_cli.weekly_operations_knowledge import WeeklyEmphasis, WeeklyPrincipleId

from .nutrition_weekly_owner_ledger_types import (
    WeeklyGenerationLedgerError,
    WeeklyGenerationOutcome,
    WeeklyGenerationRow,
    WeeklyGenerationState,
)

_SCHEMA: Final = "nutricoach-weekly-owner-generation-v2"
LedgerValue: TypeAlias = str | int | bool | list[str]


class _RowInput(BaseModel):
    model_config: ClassVar[ConfigDict] = ConfigDict(frozen=True, extra="forbid", strict=True)

    schema_: str = Field(alias="schema")
    logical_key: str
    request_base_digest: str
    customer_digest: str
    week_start: str
    owner_route_digest: str
    authority_digest: str
    aggregate_digest: str
    source_authority_digest: str
    storage_binding_digest: str
    candidate_digest: str
    config_digest: str
    consent_digest: str
    request_digest: str
    ordinal: int
    state: str
    outcome: str
    principle_ids: list[str]
    emphasis: str
    fallback: bool
    source_digest: str
    draft_id: str
    predecessor: str
    row_digest: str


def encode_row(
    base: WeeklyGenerationRow, state: WeeklyGenerationState,
    outcome: WeeklyGenerationOutcome, ordinal: int, request_digest: str,
    principle_ids: tuple[WeeklyPrincipleId, ...], emphasis: WeeklyEmphasis | None,
    fallback: bool, source_digest: str, draft_id: str, predecessor: str,
) -> bytes:
    payload = _payload(
        base, state, outcome, ordinal, request_digest, principle_ids,
        emphasis, fallback, source_digest, draft_id, predecessor,
    )
    return json.dumps(
        {**payload, "row_digest": _digest(payload)}, ensure_ascii=False,
        sort_keys=True, separators=(",", ":"),
    ).encode() + b"\n"


def parse_row(encoded: bytes, predecessor: str) -> WeeklyGenerationRow:
    try:
        raw = _RowInput.model_validate_json(encoded)
    except ValidationError as error:
        raise WeeklyGenerationLedgerError("JSON") from error
    payload = _raw_payload(raw)
    if raw.schema_ != _SCHEMA or raw.row_digest != _digest(payload) or raw.predecessor != predecessor:
        raise WeeklyGenerationLedgerError("hash chain")
    try:
        state = WeeklyGenerationState(raw.state)
        outcome = WeeklyGenerationOutcome(raw.outcome)
        principles = tuple(WeeklyPrincipleId(value) for value in raw.principle_ids)
        emphasis = None if not raw.emphasis else WeeklyEmphasis(raw.emphasis)
    except ValueError as error:
        raise WeeklyGenerationLedgerError("finite field") from error
    digests = (
        raw.logical_key, raw.request_base_digest, raw.customer_digest,
        raw.owner_route_digest, raw.authority_digest, raw.aggregate_digest,
        raw.source_authority_digest, raw.candidate_digest, raw.config_digest,
        raw.storage_binding_digest,
        raw.consent_digest, raw.request_digest,
    )
    if any(len(value) != 64 for value in digests) or any(value and len(value) != 64 for value in (raw.source_digest, raw.predecessor, raw.row_digest)):
        raise WeeklyGenerationLedgerError("digest")
    return WeeklyGenerationRow(
        raw.logical_key, raw.request_base_digest, raw.customer_digest,
        raw.week_start, raw.owner_route_digest, raw.authority_digest,
        raw.aggregate_digest, raw.source_authority_digest, raw.storage_binding_digest, raw.candidate_digest,
        raw.config_digest, raw.consent_digest, raw.request_digest, raw.ordinal,
        state, outcome, principles, emphasis, raw.fallback, raw.source_digest,
        raw.draft_id, raw.predecessor, raw.row_digest,
    )


def _payload(
    base: WeeklyGenerationRow, state: WeeklyGenerationState,
    outcome: WeeklyGenerationOutcome, ordinal: int, request_digest: str,
    principle_ids: tuple[WeeklyPrincipleId, ...], emphasis: WeeklyEmphasis | None,
    fallback: bool, source_digest: str, draft_id: str, predecessor: str,
) -> dict[str, LedgerValue]:
    return {
        "schema": _SCHEMA, "logical_key": base.logical_key,
        "request_base_digest": base.request_base_digest,
        "customer_digest": base.customer_digest, "week_start": base.week_start,
        "owner_route_digest": base.owner_route_digest, "authority_digest": base.authority_digest,
        "aggregate_digest": base.aggregate_digest,
        "source_authority_digest": base.source_authority_digest,
        "storage_binding_digest": base.storage_binding_digest,
        "candidate_digest": base.candidate_digest, "config_digest": base.config_digest,
        "consent_digest": base.consent_digest, "request_digest": request_digest,
        "ordinal": ordinal, "state": state.value, "outcome": outcome.value,
        "principle_ids": [item.value for item in principle_ids],
        "emphasis": "" if emphasis is None else emphasis.value,
        "fallback": fallback, "source_digest": source_digest,
        "draft_id": draft_id, "predecessor": predecessor,
    }


def _raw_payload(raw: _RowInput) -> dict[str, LedgerValue]:
    return {
        "schema": raw.schema_, "logical_key": raw.logical_key,
        "request_base_digest": raw.request_base_digest,
        "customer_digest": raw.customer_digest, "week_start": raw.week_start,
        "owner_route_digest": raw.owner_route_digest, "authority_digest": raw.authority_digest,
        "aggregate_digest": raw.aggregate_digest, "source_authority_digest": raw.source_authority_digest,
        "storage_binding_digest": raw.storage_binding_digest,
        "candidate_digest": raw.candidate_digest, "config_digest": raw.config_digest,
        "consent_digest": raw.consent_digest, "request_digest": raw.request_digest,
        "ordinal": raw.ordinal, "state": raw.state, "outcome": raw.outcome,
        "principle_ids": raw.principle_ids, "emphasis": raw.emphasis,
        "fallback": raw.fallback, "source_digest": raw.source_digest,
        "draft_id": raw.draft_id, "predecessor": raw.predecessor,
    }


def _digest(value: dict[str, LedgerValue]) -> str:
    encoded = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(encoded.encode()).hexdigest()
