"""Determinism and validation checks for normalized contracts."""

import json
from datetime import UTC, datetime
from pathlib import Path

import pytest
from hypothesis import given
from hypothesis import strategies as st
from pydantic import ValidationError

from app.contracts.model import Snapshot, canonical_json
from app.contracts.normalize import normalize_snapshot
from app.contracts.source import ApiViewerParser

FIXTURE = Path(__file__).parents[1] / "fixtures" / "api-viewer" / "pve-9.2.3-version.json"
RETRIEVED_AT = datetime(2026, 7, 12, 20, 8, 59, tzinfo=UTC)


def make_snapshot() -> Snapshot:
    raw = FIXTURE.read_bytes()
    parsed = ApiViewerParser().parse(raw)
    snapshot, _ = normalize_snapshot(
        parsed, raw=raw, source_version="9.2.3", retrieved_at=RETRIEVED_AT
    )
    return snapshot


def test_normalization_is_deterministic_and_round_trips() -> None:
    first = make_snapshot()
    second = make_snapshot()

    assert first.canonical_bytes() == second.canonical_bytes()
    assert first.checksum() == second.checksum()
    assert Snapshot.model_validate_json(first.canonical_bytes()) == first
    assert first.paths[0].methods[0].checksum == second.paths[0].methods[0].checksum


def test_snapshot_validates_declared_counts() -> None:
    data = make_snapshot().model_dump(mode="json")
    data["method_count"] = 99

    with pytest.raises(ValidationError, match="method_count"):
        Snapshot.model_validate(data)


def test_unknown_schema_fields_are_retained() -> None:
    raw = json.dumps(
        [
            {
                "path": "/future",
                "info": {
                    "GET": {
                        "name": "future",
                        "returns": {"type": "string", "futureKeyword": {"x": 1}},
                    }
                },
            }
        ]
    ).encode()
    snapshot, _ = normalize_snapshot(
        ApiViewerParser().parse(raw),
        raw=raw,
        source_version="test",
        retrieved_at=RETRIEVED_AT,
    )

    assert snapshot.paths[0].methods[0].returns.extra["futureKeyword"] == {"x": 1}


def test_nullable_source_collections_normalize_as_empty() -> None:
    raw = (
        b'[{"path":"/nullable","info":{"GET":{"parameters":{"properties":null},'
        b'"returns":{"type":"string","enum":null}}}}]'
    )
    snapshot, _ = normalize_snapshot(
        ApiViewerParser().parse(raw),
        raw=raw,
        source_version="test",
        retrieved_at=RETRIEVED_AT,
    )

    method = snapshot.paths[0].methods[0]
    assert method.parameters == ()
    assert method.returns.enum == ()


@given(st.dictionaries(st.text(min_size=1), st.integers(), max_size=10))
def test_canonical_json_is_independent_of_mapping_order(values: dict[str, int]) -> None:
    reversed_values = dict(reversed(tuple(values.items())))

    assert canonical_json(values) == canonical_json(reversed_values)
