"""Tests for Model Governance schema self-validation.

Covers the five new governance schemas:
- model_registry.schema.json
- routing_policy.schema.json
- project_profile.schema.json
- routing_decision_record.schema.json
- budget_state.schema.json
"""

from __future__ import annotations

import json

import pytest

from abi_control_core.contracts import CONTRACTS_DIR
from abi_control_core.sdk.validate import SchemaValidationError, validate_json

# ── Schema filenames ──────────────────────────────────────────────────────

GOVERNANCE_SCHEMAS = [
    "model_registry.schema.json",
    "routing_policy.schema.json",
    "project_profile.schema.json",
    "routing_decision_record.schema.json",
    "budget_state.schema.json",
]

# ── Valid fixtures ────────────────────────────────────────────────────────

VALID_MODEL_REGISTRY = {
    "schema_version": "model-registry/1.0",
    "providers": [
        {
            "provider_id": "anthropic",
            "display_name": "Anthropic",
            "channels": [
                {
                    "channel_id": "api-default",
                    "channel_type": "api",
                    "models": [
                        {
                            "model_id": "claude-opus-4-6",
                            "display_name": "Claude Opus 4.6",
                            "capabilities": ["code_generation", "reasoning"],
                            "tier": "frontier",
                            "context_window": 200000,
                            "max_output_tokens": 32000,
                            "ru_per_1k_input": 15.0,
                            "ru_per_1k_output": 75.0,
                        }
                    ],
                }
            ],
        }
    ],
}

VALID_ROUTING_POLICY = {
    "schema_version": "routing-policy/1.0",
    "policy_id": "default-policy",
    "description": "Default routing policy for all projects.",
    "rules": [
        {
            "rule_id": "code-gen-frontier",
            "priority": 0,
            "match": {
                "task_type": "code_generation",
                "tier_required": "frontier",
                "capabilities_required": ["code_generation"],
            },
            "route_to": {
                "model_ref": "anthropic/api-default/claude-opus-4-6",
                "fallback_chain": "frontier-fallback",
            },
        }
    ],
    "fallback_chains": {
        "frontier-fallback": [
            "anthropic/api-default/claude-opus-4-6",
            "openai/api-default/gpt-4o",
        ]
    },
}

VALID_PROJECT_PROFILE = {
    "schema_version": "project-profile/1.0",
    "profile_id": "abi-core",
    "description": "Routing profile for the abi-core repository.",
    "constraints": {
        "no_external_models": False,
        "require_frontier": False,
        "max_concurrency": 4,
        "max_ru_per_day": 10000.0,
    },
    "preferred_models": ["anthropic/api-default/claude-opus-4-6"],
    "shadow_eval": {
        "enabled": False,
    },
}

VALID_ROUTING_DECISION_RECORD = {
    "schema_version": "routing-decision-record/1.0",
    "decision_id": "dec-20260228-001",
    "decided_at": "2026-02-28T10:00:00Z",
    "task": {
        "task_type": "code_generation",
        "capabilities_required": ["code_generation"],
        "tier_required": "frontier",
        "estimated_input_tokens": 5000,
        "estimated_output_tokens": 2000,
    },
    "selected": {
        "model_ref": "anthropic/api-default/claude-opus-4-6",
        "channel_id": "api-default",
        "provider_id": "anthropic",
        "tier": "frontier",
        "ru_estimate": 225.0,
    },
    "matched_rule_id": "code-gen-frontier",
    "constraints_applied": ["no_external_models"],
    "quota_snapshot": {
        "zone": "GREEN",
        "ru_consumed": 1500.0,
        "ru_remaining": 8500.0,
        "ru_allocated": 10000.0,
    },
    "fallback_used": False,
}

VALID_BUDGET_STATE = {
    "schema_version": "budget-state/1.0",
    "budget_id": "daily-default",
    "ru_allocated": 10000.0,
    "ru_consumed": 1500.0,
    "ru_remaining": 8500.0,
    "zone": "GREEN",
    "thresholds": {
        "yellow_pct": 60.0,
        "red_pct": 80.0,
        "exhausted_pct": 95.0,
    },
    "concurrency": {
        "active_slots": 1,
        "max_slots": 4,
    },
    "window": {
        "period": "daily",
        "window_start": "2026-02-28T00:00:00Z",
        "window_end": "2026-02-28T23:59:59Z",
    },
    "recorded_at": "2026-02-28T10:00:00Z",
}

# ── Parametrized valid-fixture tests ──────────────────────────────────────

ALL_GOVERNANCE_FIXTURES = [
    ("model_registry.schema.json", VALID_MODEL_REGISTRY),
    ("routing_policy.schema.json", VALID_ROUTING_POLICY),
    ("project_profile.schema.json", VALID_PROJECT_PROFILE),
    ("routing_decision_record.schema.json", VALID_ROUTING_DECISION_RECORD),
    ("budget_state.schema.json", VALID_BUDGET_STATE),
]


# ── Positive tests: valid fixture validates ───────────────────────────────


def test_validate_model_registry_valid() -> None:
    """model_registry valid fixture passes validation."""
    validate_json(VALID_MODEL_REGISTRY, "model_registry.schema.json")


def test_validate_routing_policy_valid() -> None:
    """routing_policy valid fixture passes validation."""
    validate_json(VALID_ROUTING_POLICY, "routing_policy.schema.json")


def test_validate_project_profile_valid() -> None:
    """project_profile valid fixture passes validation."""
    validate_json(VALID_PROJECT_PROFILE, "project_profile.schema.json")


def test_validate_routing_decision_record_valid() -> None:
    """routing_decision_record valid fixture passes validation."""
    validate_json(VALID_ROUTING_DECISION_RECORD, "routing_decision_record.schema.json")


def test_validate_budget_state_valid() -> None:
    """budget_state valid fixture passes validation."""
    validate_json(VALID_BUDGET_STATE, "budget_state.schema.json")


# ── Negative tests: missing required field ────────────────────────────────


def test_validate_model_registry_missing_required() -> None:
    """model_registry without required 'providers' must fail."""
    invalid = {**VALID_MODEL_REGISTRY}
    del invalid["providers"]
    with pytest.raises(SchemaValidationError) as exc_info:
        validate_json(invalid, "model_registry.schema.json")
    assert any("providers" in msg for _, msg in exc_info.value.errors)


def test_validate_routing_policy_missing_required() -> None:
    """routing_policy without required 'rules' must fail."""
    invalid = {**VALID_ROUTING_POLICY}
    del invalid["rules"]
    with pytest.raises(SchemaValidationError) as exc_info:
        validate_json(invalid, "routing_policy.schema.json")
    assert any("rules" in msg for _, msg in exc_info.value.errors)


def test_validate_project_profile_missing_required() -> None:
    """project_profile without required 'profile_id' must fail."""
    invalid = {**VALID_PROJECT_PROFILE}
    del invalid["profile_id"]
    with pytest.raises(SchemaValidationError) as exc_info:
        validate_json(invalid, "project_profile.schema.json")
    assert any("profile_id" in msg for _, msg in exc_info.value.errors)


def test_validate_routing_decision_record_missing_required() -> None:
    """routing_decision_record without required 'quota_snapshot' must fail."""
    invalid = {**VALID_ROUTING_DECISION_RECORD}
    del invalid["quota_snapshot"]
    with pytest.raises(SchemaValidationError) as exc_info:
        validate_json(invalid, "routing_decision_record.schema.json")
    assert any("quota_snapshot" in msg for _, msg in exc_info.value.errors)


def test_validate_budget_state_missing_required() -> None:
    """budget_state without required 'thresholds' must fail."""
    invalid = {**VALID_BUDGET_STATE}
    del invalid["thresholds"]
    with pytest.raises(SchemaValidationError) as exc_info:
        validate_json(invalid, "budget_state.schema.json")
    assert any("thresholds" in msg for _, msg in exc_info.value.errors)


# ── Negative tests: extra field rejected (additionalProperties: false) ────


def test_validate_model_registry_rejects_extra_field() -> None:
    """model_registry rejects unknown top-level field."""
    invalid = {**VALID_MODEL_REGISTRY, "unexpected_field": "bad"}
    with pytest.raises(SchemaValidationError):
        validate_json(invalid, "model_registry.schema.json")


def test_validate_routing_policy_rejects_extra_field() -> None:
    """routing_policy rejects unknown top-level field."""
    invalid = {**VALID_ROUTING_POLICY, "unexpected_field": "bad"}
    with pytest.raises(SchemaValidationError):
        validate_json(invalid, "routing_policy.schema.json")


def test_validate_project_profile_rejects_extra_field() -> None:
    """project_profile rejects unknown top-level field."""
    invalid = {**VALID_PROJECT_PROFILE, "unexpected_field": "bad"}
    with pytest.raises(SchemaValidationError):
        validate_json(invalid, "project_profile.schema.json")


def test_validate_routing_decision_record_rejects_extra_field() -> None:
    """routing_decision_record rejects unknown top-level field."""
    invalid = {**VALID_ROUTING_DECISION_RECORD, "unexpected_field": "bad"}
    with pytest.raises(SchemaValidationError):
        validate_json(invalid, "routing_decision_record.schema.json")


def test_validate_budget_state_rejects_extra_field() -> None:
    """budget_state rejects unknown top-level field."""
    invalid = {**VALID_BUDGET_STATE, "unexpected_field": "bad"}
    with pytest.raises(SchemaValidationError):
        validate_json(invalid, "budget_state.schema.json")


# ── Cross-schema structural tests ────────────────────────────────────────


_SCHEMA_IDS = [s.replace(".schema.json", "") for s in GOVERNANCE_SCHEMAS]


@pytest.mark.parametrize(
    "schema_file",
    GOVERNANCE_SCHEMAS,
    ids=_SCHEMA_IDS,
)
def test_all_governance_schemas_are_draft_2020_12(
    schema_file: str,
) -> None:
    """Every governance schema must declare $schema as draft/2020-12."""
    path = CONTRACTS_DIR / schema_file
    with open(path, encoding="utf-8") as f:
        schema = json.load(f)
    assert schema["$schema"] == "https://json-schema.org/draft/2020-12/schema"


@pytest.mark.parametrize(
    "schema_file",
    GOVERNANCE_SCHEMAS,
    ids=_SCHEMA_IDS,
)
def test_all_governance_schemas_have_additional_properties_false(
    schema_file: str,
) -> None:
    """Every governance schema must set additionalProperties: false at top level."""
    path = CONTRACTS_DIR / schema_file
    with open(path, encoding="utf-8") as f:
        schema = json.load(f)
    assert schema.get("additionalProperties") is False
