"""Tests for domain value objects: MitreId and ScoringWeights."""

import sys
import os

backend_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if backend_dir not in sys.path:
    sys.path.insert(0, backend_dir)

import pytest

from app.domain.value_objects.mitre_id import MitreId
from app.domain.value_objects.scoring_weights import ScoringWeights


# ── MitreId ──────────────────────────────────────────────────────────


class TestMitreId:

    def test_valid_technique(self):
        mid = MitreId("T1059")
        assert mid.value == "T1059"
        assert str(mid) == "T1059"
        assert not mid.is_subtechnique
        assert mid.parent_id is None

    def test_valid_subtechnique(self):
        mid = MitreId("T1059.001")
        assert mid.value == "T1059.001"
        assert mid.is_subtechnique
        assert mid.parent_id == "T1059"

    def test_invalid_empty_string(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("")

    def test_invalid_no_prefix(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("1059")

    def test_invalid_wrong_prefix(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("A1059")

    def test_invalid_too_few_digits(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("T105")

    def test_invalid_subtechnique_format(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("T1059.01")  # needs 3 digits after dot

    def test_invalid_trailing_garbage(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            MitreId("T1059.001.002")

    def test_equality_with_same_mitre_id(self):
        assert MitreId("T1059") == MitreId("T1059")

    def test_equality_with_string(self):
        assert MitreId("T1059") == "T1059"

    def test_inequality(self):
        assert MitreId("T1059") != MitreId("T1060")

    def test_hashable(self):
        s = {MitreId("T1059"), MitreId("T1059"), MitreId("T1060")}
        assert len(s) == 2

    def test_immutable(self):
        mid = MitreId("T1059")
        with pytest.raises(AttributeError):
            mid.value = "T1060"


# ── ScoringWeights ───────────────────────────────────────────────────


class TestScoringWeights:

    def test_valid_default(self):
        w = ScoringWeights.default()
        assert w.tests == 40.0
        assert w.detection_rules == 25.0
        assert w.d3fend == 15.0
        assert w.recency == 10.0
        assert w.severity == 10.0

    def test_valid_custom(self):
        w = ScoringWeights(
            tests=50, detection_rules=20, d3fend=10,
            recency=10, severity=10,
        )
        assert w.tests == 50

    def test_invalid_sum_not_100(self):
        with pytest.raises(ValueError, match="sum to 100"):
            ScoringWeights(
                tests=50, detection_rules=20, d3fend=10,
                recency=10, severity=5,
            )

    def test_invalid_negative_weight(self):
        with pytest.raises(ValueError, match="non-negative"):
            ScoringWeights(
                tests=-10, detection_rules=40, d3fend=30,
                recency=20, severity=20,
            )

    def test_immutable(self):
        w = ScoringWeights.default()
        with pytest.raises(AttributeError):
            w.tests = 50
