"""Tests for complyform.core.profiles.models — Pydantic validation paths."""

from __future__ import annotations

import pytest
from pydantic import ValidationError

from complyform.core.profiles.models import (
    Control,
    ControlCrossMapping,
    ControlsFile,
    CrossMappingIndex,
    FrameworkMeta,
    FrameworkRegistry,
    MappingFile,
    NotApplicableEntry,
    NotApplicableFile,
    ResourceMapping,
)

# -------------------------------------------------------------------
# FrameworkMeta
# -------------------------------------------------------------------


class TestFrameworkMeta:
    def test_all_fields(self) -> None:
        fm = FrameworkMeta(
            id="soc2",
            name="SOC 2",
            version="2017",
            authority="AICPA",
            tier="community",
            cloud="all",
            category="global",
            control_count=64,
            description="Trust Services Criteria",
            url="https://aicpa.org",
        )
        assert fm.id == "soc2"
        assert fm.description == "Trust Services Criteria"
        assert fm.url == "https://aicpa.org"

    def test_optional_defaults(self) -> None:
        fm = FrameworkMeta(
            id="x",
            name="X",
            version="1",
            authority="A",
            tier="community",
            cloud="all",
            category="global",
            control_count=1,
        )
        assert fm.description == ""
        assert fm.url == ""

    def test_missing_required_field(self) -> None:
        with pytest.raises(ValidationError, match="control_count"):
            FrameworkMeta(  # type: ignore[call-arg]
                id="x",
                name="X",
                version="1",
                authority="A",
                tier="community",
                cloud="all",
                category="global",
            )

    def test_wrong_type_control_count(self) -> None:
        with pytest.raises(ValidationError):
            FrameworkMeta(
                id="x",
                name="X",
                version="1",
                authority="A",
                tier="community",
                cloud="all",
                category="global",
                control_count="not_a_number",  # type: ignore[arg-type]
            )

    def test_model_dump_json(self) -> None:
        fm = FrameworkMeta(
            id="soc2",
            name="SOC 2",
            version="2017",
            authority="AICPA",
            tier="community",
            cloud="all",
            category="global",
            control_count=64,
        )
        d = fm.model_dump(mode="json")
        assert d["id"] == "soc2"
        assert isinstance(d["control_count"], int)


# -------------------------------------------------------------------
# FrameworkRegistry
# -------------------------------------------------------------------


class TestFrameworkRegistry:
    def test_valid(self) -> None:
        reg = FrameworkRegistry(
            schema_version=1,
            frameworks=[
                FrameworkMeta(
                    id="x",
                    name="X",
                    version="1",
                    authority="A",
                    tier="community",
                    cloud="all",
                    category="global",
                    control_count=1,
                ),
            ],
        )
        assert len(reg.frameworks) == 1

    def test_empty_frameworks_list(self) -> None:
        reg = FrameworkRegistry(schema_version=1, frameworks=[])
        assert reg.frameworks == []

    def test_missing_schema_version(self) -> None:
        with pytest.raises(ValidationError, match="schema_version"):
            FrameworkRegistry.model_validate({"frameworks": []})

    def test_invalid_framework_in_list(self) -> None:
        with pytest.raises(ValidationError):
            FrameworkRegistry.model_validate(
                {
                    "schema_version": 1,
                    "frameworks": [{"id": "x"}],  # missing fields
                }
            )


# -------------------------------------------------------------------
# Control
# -------------------------------------------------------------------


class TestControl:
    def test_all_fields(self) -> None:
        cd = Control(
            id="CC6.1",
            title="Logical Access",
            description="desc",
            severity="critical",
            section="CC6",
            family="Common Criteria",
        )
        assert cd.severity == "critical"
        assert cd.section == "CC6"

    def test_defaults(self) -> None:
        cd = Control(id="X", title="Y")
        assert cd.description == ""
        assert cd.severity == "medium"
        assert cd.section == ""
        assert cd.family == ""

    def test_missing_title(self) -> None:
        with pytest.raises(ValidationError, match="title"):
            Control(id="X")  # type: ignore[call-arg]


# -------------------------------------------------------------------
# ControlsFile
# -------------------------------------------------------------------


class TestControlsFile:
    def test_valid(self) -> None:
        cf = ControlsFile(
            schema_version=1,
            framework_id="soc2",
            framework_version="2017",
            controls=[Control(id="CC1.1", title="Control")],
        )
        assert len(cf.controls) == 1

    def test_missing_framework_id(self) -> None:
        with pytest.raises(ValidationError, match="framework_id"):
            ControlsFile.model_validate(
                {
                    "schema_version": 1,
                    "framework_version": "1",
                    "controls": [],
                }
            )

    def test_invalid_control_entry(self) -> None:
        with pytest.raises(ValidationError):
            ControlsFile.model_validate(
                {
                    "schema_version": 1,
                    "framework_id": "test",
                    "framework_version": "1",
                    "controls": [{"id": 123}],  # title missing, id wrong type
                }
            )


# -------------------------------------------------------------------
# ResourceMapping
# -------------------------------------------------------------------


class TestResourceMapping:
    def test_all_fields(self) -> None:
        me = ResourceMapping(
            check_id="CKV_GCP_1",
            control_id="CC6.1",
            resource_types=["google_compute_firewall"],
            native_id="SCC-123",
            enforcement_tier="preventive",
            confidence="verified",
        )
        assert me.native_id == "SCC-123"
        assert me.enforcement_tier == "preventive"

    def test_defaults(self) -> None:
        me = ResourceMapping(check_id="X", control_id="Y")
        assert me.resource_types == []
        assert me.native_id == ""
        assert me.enforcement_tier == "runtime"
        assert me.confidence == "high"

    def test_missing_check_id(self) -> None:
        with pytest.raises(ValidationError, match="check_id"):
            ResourceMapping(control_id="Y")  # type: ignore[call-arg]


# -------------------------------------------------------------------
# MappingFile
# -------------------------------------------------------------------


class TestMappingFile:
    def test_valid(self) -> None:
        mf = MappingFile(
            schema_version=1,
            framework_id="soc2",
            cloud="gcp",
            mappings=[ResourceMapping(check_id="CKV_GCP_1", control_id="CC6.1")],
        )
        assert mf.cloud == "gcp"

    def test_missing_cloud(self) -> None:
        with pytest.raises(ValidationError, match="cloud"):
            MappingFile.model_validate(
                {
                    "schema_version": 1,
                    "framework_id": "soc2",
                    "mappings": [],
                }
            )

    def test_wrong_type_in_mappings(self) -> None:
        with pytest.raises(ValidationError):
            MappingFile.model_validate(
                {
                    "schema_version": 1,
                    "framework_id": "soc2",
                    "cloud": "gcp",
                    "mappings": ["not_a_dict"],
                }
            )


# -------------------------------------------------------------------
# ControlCrossMapping
# -------------------------------------------------------------------


class TestControlCrossMapping:
    def test_all_fields(self) -> None:
        cm = ControlCrossMapping(
            source_framework="soc2",
            source_control="CC6.1",
            target_framework="iso27001",
            target_control="A.8.3",
            relationship="parent",
        )
        assert cm.relationship == "parent"

    def test_default_relationship(self) -> None:
        cm = ControlCrossMapping(
            source_framework="a",
            source_control="1",
            target_framework="b",
            target_control="2",
        )
        assert cm.relationship == "equivalent"

    def test_missing_target(self) -> None:
        with pytest.raises(ValidationError, match="target_control"):
            ControlCrossMapping(  # type: ignore[call-arg]
                source_framework="a",
                source_control="1",
                target_framework="b",
            )


# -------------------------------------------------------------------
# CrossMappingIndex
# -------------------------------------------------------------------


class TestCrossMappingIndex:
    def test_valid(self) -> None:
        idx = CrossMappingIndex(schema_version=1, mappings=[])
        assert idx.mappings == []

    def test_missing_schema_version(self) -> None:
        with pytest.raises(ValidationError, match="schema_version"):
            CrossMappingIndex.model_validate({"mappings": []})


# -------------------------------------------------------------------
# NotApplicable models
# -------------------------------------------------------------------


class TestNotApplicableEntry:
    def test_valid(self) -> None:
        entry = NotApplicableEntry(
            cloud="gcp",
            resource_types=["google_project_iam_member"],
        )
        assert entry.cloud == "gcp"

    def test_missing_cloud(self) -> None:
        with pytest.raises(ValidationError, match="cloud"):
            NotApplicableEntry(  # type: ignore[call-arg]
                resource_types=["x"],
            )


class TestNotApplicableFile:
    def test_valid(self) -> None:
        naf = NotApplicableFile(
            schema_version=1,
            entries=[
                NotApplicableEntry(cloud="gcp", resource_types=["x"]),
            ],
        )
        assert len(naf.entries) == 1


# -------------------------------------------------------------------
# Signing stub
# -------------------------------------------------------------------


class TestSigningStub:
    def test_verify_returns_true(self) -> None:
        from complyform.core.profiles.signing import verify_profile

        assert verify_profile("/any/path") is True

    def test_sign_returns_empty(self) -> None:
        from complyform.core.profiles.signing import sign_profile

        assert sign_profile("/any/path", "key") == ""
