"""Tests for TechniqueEntity — pure domain logic, no DB."""

import uuid
import sys
import os
from datetime import datetime
from unittest.mock import MagicMock

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.entities.technique import TechniqueEntity
from app.domain.enums import TechniqueStatus


# ── Helpers ──────────────────────────────────────────────────────────


def _entity(**overrides) -> TechniqueEntity:
    defaults = dict(
        id=uuid.uuid4(),
        mitre_id="T1059",
        name="Command and Scripting Interpreter",
        tactic="execution",
        status_global=TechniqueStatus.not_evaluated,
    )
    defaults.update(overrides)
    return TechniqueEntity(**defaults)


def _fake_orm(**overrides) -> MagicMock:
    m = MagicMock()
    m.id = uuid.uuid4()
    m.mitre_id = "T1059"
    m.name = "Command and Scripting Interpreter"
    m.tactic = "execution"
    m.description = None
    m.platforms = ["windows", "linux"]
    m.is_subtechnique = False
    m.parent_mitre_id = None
    m.status_global = "not_evaluated"
    m.review_required = False
    m.last_review_date = None
    for k, v in overrides.items():
        setattr(m, k, v)
    return m


# ── 1. Factory: create() ────────────────────────────────────────────


class TestCreate:

    def test_valid_technique(self):
        e = TechniqueEntity.create(
            mitre_id="T1059",
            name="Command and Scripting Interpreter",
            tactic="execution",
        )
        assert e.mitre_id == "T1059"
        assert e.name == "Command and Scripting Interpreter"
        assert e.status_global == TechniqueStatus.not_evaluated
        assert not e.is_subtechnique

    def test_valid_subtechnique(self):
        e = TechniqueEntity.create(
            mitre_id="T1059.001",
            name="PowerShell",
            tactic="execution",
        )
        assert e.is_subtechnique
        assert e.parent_mitre_id == "T1059"

    def test_invalid_mitre_id_raises(self):
        with pytest.raises(ValueError, match="Invalid MITRE"):
            TechniqueEntity.create(mitre_id="INVALID", name="Bad", tactic="x")

    def test_platforms_default_to_empty(self):
        e = TechniqueEntity.create(mitre_id="T1059", name="Test")
        assert e.platforms == []


# ── 2. from_orm / apply_to ──────────────────────────────────────────


class TestOrmRoundTrip:

    def test_from_orm_basic(self):
        orm = _fake_orm()
        e = TechniqueEntity.from_orm(orm)
        assert e.mitre_id == "T1059"
        assert e.status_global == TechniqueStatus.not_evaluated

    def test_from_orm_coerces_string_status(self):
        orm = _fake_orm(status_global="validated")
        e = TechniqueEntity.from_orm(orm)
        assert e.status_global == TechniqueStatus.validated

    def test_apply_to_updates_model(self):
        orm = _fake_orm()
        e = TechniqueEntity.from_orm(orm)
        e.status_global = TechniqueStatus.validated
        e.review_required = True
        e.apply_to(orm)
        assert orm.status_global == TechniqueStatus.validated
        assert orm.review_required is True


# ── 3. recalculate_status ───────────────────────────────────────────


class TestRecalculateStatus:

    def test_no_tests_gives_not_evaluated(self):
        e = _entity()
        result = e.recalculate_status([])
        assert result == TechniqueStatus.not_evaluated
        assert e.status_global == TechniqueStatus.not_evaluated

    def test_all_validated_all_detected_gives_validated(self):
        e = _entity()
        tests = [("validated", "detected"), ("validated", "detected")]
        assert e.recalculate_status(tests) == TechniqueStatus.validated

    def test_all_validated_some_partially_gives_partial(self):
        e = _entity()
        tests = [("validated", "detected"), ("validated", "partially_detected")]
        assert e.recalculate_status(tests) == TechniqueStatus.partial

    def test_all_validated_none_detected_gives_not_covered(self):
        e = _entity()
        tests = [("validated", "not_detected"), ("validated", "not_detected")]
        assert e.recalculate_status(tests) == TechniqueStatus.not_covered

    def test_all_validated_mix_detected_and_not_detected_gives_partial(self):
        e = _entity()
        tests = [
            ("validated", "detected"),
            ("validated", "detected"),
            ("validated", "detected"),
            ("validated", "detected"),
            ("validated", "detected"),
            ("validated", "not_detected"),
        ]
        assert e.recalculate_status(tests) == TechniqueStatus.partial

    def test_all_validated_no_results_gives_not_covered(self):
        e = _entity()
        tests = [("validated", None)]
        assert e.recalculate_status(tests) == TechniqueStatus.not_covered

    def test_mixed_validated_and_in_progress_gives_partial(self):
        e = _entity()
        tests = [("validated", "detected"), ("draft", None)]
        assert e.recalculate_status(tests) == TechniqueStatus.partial

    def test_all_in_progress_gives_in_progress(self):
        e = _entity()
        tests = [("draft", None), ("red_executing", None)]
        assert e.recalculate_status(tests) == TechniqueStatus.in_progress


# ── 4. mark_reviewed / flag_for_review ──────────────────────────────


class TestReviewCycle:

    def test_mark_reviewed_clears_flag(self):
        e = _entity(review_required=True)
        e.mark_reviewed()
        assert e.review_required is False
        assert e.last_review_date is not None

    def test_flag_for_review(self):
        e = _entity(review_required=False)
        e.flag_for_review()
        assert e.review_required is True
