"""Tests for :mod:`mgf.vm.forensics.case` (Phase 17, FN-A1.1)."""

from __future__ import annotations

from datetime import UTC, datetime
from pathlib import Path
from uuid import UUID, uuid4

import pytest

from mgf.vm.forensics import (
    CASE_SCHEMA_VERSION,
    Artifact,
    ArtifactKind,
    Case,
    CaseStatus,
)


def _make_case(*, name: str = "vm-1") -> Case:
    return Case(
        name=name,
        case_id=uuid4(),
        vm_name="suspect-1",
        sample_path=Path("/tmp/sample.exe"),
        sample_sha256="deadbeef",
        description="hi",
        created_at=datetime(2026, 4, 26, 12, 0, tzinfo=UTC),
        vmanager_version="0.1.0dev0",
        qemu_version="9.0.0",
        host_kernel="6.5.0",
    )


def _make_artifact(*, kind: ArtifactKind = ArtifactKind.NETWORK_PCAP) -> Artifact:
    return Artifact(
        kind=kind,
        path=Path("/tmp/x"),
        sha256="abc",
        size_bytes=42,
        captured_at=datetime(2026, 4, 26, 12, 5, tzinfo=UTC),
        captured_by="tcpdump 4.99.1",
        metadata={"bpf": "tcp port 80"},
    )


class TestSchemaVersion:
    def test_pinned_to_one(self) -> None:
        assert CASE_SCHEMA_VERSION == 1


class TestCaseStatus:
    def test_values(self) -> None:
        assert CaseStatus.PREPARING.value == "preparing"
        assert CaseStatus.ACTIVE.value == "active"
        assert CaseStatus.COMPLETED.value == "completed"
        assert CaseStatus.ARCHIVED.value == "archived"

    def test_is_str_enum_round_trip(self) -> None:
        # YAML round-trip safety.
        assert CaseStatus("preparing") == CaseStatus.PREPARING


class TestArtifactKind:
    def test_dotted_naming(self) -> None:
        # Every kind has a dotted "category.subject" form for routing.
        for kind in ArtifactKind:
            assert "." in kind.value


class TestCaseDataclass:
    def test_default_status_is_preparing(self) -> None:
        c = _make_case()
        assert c.status == CaseStatus.PREPARING

    def test_default_no_artifacts(self) -> None:
        assert _make_case().artifacts == ()

    def test_total_size_bytes_sums(self) -> None:
        c = _make_case()
        with_two = c.with_artifact(_make_artifact()).with_artifact(_make_artifact())
        # Both artifacts have size 42.
        assert with_two.total_size_bytes() == 84

    def test_with_artifact_returns_new_case_immutable(self) -> None:
        original = _make_case()
        a = _make_artifact()
        new = original.with_artifact(a)
        assert original.artifacts == ()
        assert new.artifacts == (a,)

    def test_with_status_preserves_other_fields(self) -> None:
        original = _make_case()
        new = original.with_status(CaseStatus.ACTIVE)
        assert new.status == CaseStatus.ACTIVE
        assert new.case_id == original.case_id
        assert new.vm_name == original.vm_name
        assert new.created_at == original.created_at

    def test_with_status_completed_sets_completed_at(self) -> None:
        original = _make_case()
        when = datetime(2026, 5, 1, tzinfo=UTC)
        new = original.with_status(CaseStatus.COMPLETED, completed_at=when)
        assert new.completed_at == when

    def test_dataclass_is_frozen(self) -> None:
        c = _make_case()
        with pytest.raises((AttributeError, Exception)):
            c.status = CaseStatus.ACTIVE  # type: ignore[misc]

    def test_case_id_round_trips_through_uuid(self) -> None:
        cid = UUID("12345678-1234-5678-1234-567812345678")
        c = Case(name="x", case_id=cid, vm_name="vm")
        assert c.case_id == cid


class TestArtifactDataclass:
    def test_default_metadata_is_empty(self) -> None:
        a = Artifact(
            kind=ArtifactKind.MEMORY_DUMP,
            path=Path("/tmp/m"),
            sha256="x",
            size_bytes=0,
            captured_at=datetime.now(UTC),
        )
        assert dict(a.metadata) == {}
        assert a.captured_by == ""

    def test_is_frozen(self) -> None:
        a = _make_artifact()
        with pytest.raises((AttributeError, Exception)):
            a.size_bytes = 0  # type: ignore[misc]
