"""Private canonical JSON and structural validators for readiness artifacts."""

from __future__ import annotations

import hashlib
import json
import stat
from collections.abc import Mapping
from pathlib import Path
from typing import cast

from checkin_cli.nutrition_readiness_contract import DIGEST_PATTERN


def canonical_digest(document: Mapping[str, object]) -> str:
    payload = {key: value for key, value in document.items() if key != "digest"}
    encoded = json.dumps(
        payload,
        ensure_ascii=False,
        separators=(",", ":"),
        sort_keys=True,
    ).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()


def load_document(path: Path, root: Path) -> Mapping[str, object] | None:
    try:
        resolved = path.resolve(strict=True)
        if path.is_symlink() or not resolved.is_relative_to(root):
            return None
        info = resolved.stat()
        if (
            not resolved.is_file()
            or info.st_nlink != 1
            or stat.S_IMODE(info.st_mode) & 0o077
        ):
            return None
        parsed = cast(object, json.loads(resolved.read_text(encoding="utf-8")))
    except (OSError, ValueError, json.JSONDecodeError):
        return None
    return cast(Mapping[str, object], parsed) if isinstance(parsed, dict) else None


def valid_digest(document: Mapping[str, object]) -> bool:
    declared = document.get("digest")
    return (
        isinstance(declared, str)
        and DIGEST_PATTERN.fullmatch(declared) is not None
        and declared == canonical_digest(document)
    )


def required(document: Mapping[str, object], fields: frozenset[str]) -> bool:
    return fields.issubset(document)


def positive_number(value: object) -> bool:
    return (
        isinstance(value, (int, float))
        and not isinstance(value, bool)
        and value > 0
    )


def number_in_range(value: object, minimum: float, maximum: float) -> bool:
    return positive_number(value) and minimum <= cast(float, value) <= maximum


def valid_rule_list(value: object) -> bool:
    if not isinstance(value, list) or not value:
        return False
    for candidate in cast(list[object], value):
        if not isinstance(candidate, dict):
            return False
        rule = cast(Mapping[str, object], candidate)
        if (
            not rule.get("rule_id")
            or rule.get("action") not in {"exclude", "require_human_review", "inform"}
            or not rule.get("severity")
            or not rule.get("applicability")
            or not isinstance(rule.get("source_ids"), list)
            or not rule["source_ids"]
        ):
            return False
    return True


def valid_week_targets(
    weeks: object,
    minimum_calories: object,
    maximum_calories: object,
) -> bool:
    if not isinstance(weeks, list):
        return False
    week_items = cast(list[object], weeks)
    if (
        len(week_items) != 12
        or not positive_number(minimum_calories)
        or not positive_number(maximum_calories)
        or cast(float, minimum_calories) >= cast(float, maximum_calories)
    ):
        return False
    for expected_week, candidate in enumerate(week_items, start=1):
        if not isinstance(candidate, dict):
            return False
        item = cast(Mapping[str, object], candidate)
        if item.get("week") != expected_week:
            return False
        calories = item.get("calories_kcal")
        if (
            not positive_number(calories)
            or not positive_number(item.get("protein_g"))
            or not positive_number(item.get("carbohydrate_g"))
            or not positive_number(item.get("fat_g"))
            or not cast(float, minimum_calories)
            <= cast(float, calories)
            <= cast(float, maximum_calories)
        ):
            return False
    return True
