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

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

import pytest

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)

from app.domain.entities.campaign import (
    CampaignEntity,
    CampaignStatus,
    CampaignType,
)
from app.domain.errors import BusinessRuleViolation, InvalidStateTransition


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


def _entity(status: str = "draft", test_count: int = 0, **overrides) -> CampaignEntity:
    defaults = dict(
        id=uuid.uuid4(),
        name="Test Campaign",
        type=CampaignType.custom,
        status=CampaignStatus(status),
        description=None,
        threat_actor_id=None,
        created_by=None,
        target_platform=None,
        tags=[],
        test_count=test_count,
    )
    defaults.update(overrides)
    return CampaignEntity(**defaults)


def _fake_orm(status: str = "draft", test_count: int = 0, **overrides) -> MagicMock:
    m = MagicMock()
    m.id = uuid.uuid4()
    m.name = "Test Campaign"
    m.type = "custom"
    m.status = status
    m.description = None
    m.threat_actor_id = None
    m.created_by = None
    m.target_platform = None
    m.tags = []
    m.campaign_tests = [MagicMock()] * test_count if test_count else []
    for k, v in overrides.items():
        setattr(m, k, v)
    return m


# ── 1. Test activation from draft with tests → success ───────────────


def test_activate_from_draft_with_tests_success():
    e = _entity("draft", test_count=1)
    e.activate()
    assert e.status == CampaignStatus.active


def test_activate_from_draft_with_multiple_tests_success():
    e = _entity("draft", test_count=3)
    e.activate()
    assert e.status == CampaignStatus.active


# ── 2. Test activation from draft with 0 tests → BusinessRuleViolation ───


def test_activate_from_draft_with_zero_tests_raises():
    e = _entity("draft", test_count=0)
    with pytest.raises(BusinessRuleViolation, match="at least one test"):
        e.activate()
    assert e.status == CampaignStatus.draft


# ── 3. Test activation from active → InvalidStateTransition ────────────


def test_activate_from_active_raises():
    e = _entity("active", test_count=2)
    with pytest.raises(InvalidStateTransition) as exc_info:
        e.activate()
    assert exc_info.value.current_state == "active"
    assert exc_info.value.target_state == "active"
    assert "completed" in exc_info.value.valid_transitions


# ── 4. Test complete from active → success ──────────────────────────────


def test_complete_from_active_success():
    e = _entity("active", test_count=2)
    e.complete()
    assert e.status == CampaignStatus.completed


# ── 5. Test complete from draft → InvalidStateTransition ────────────────


def test_complete_from_draft_raises():
    e = _entity("draft", test_count=1)
    with pytest.raises(InvalidStateTransition) as exc_info:
        e.complete()
    assert exc_info.value.current_state == "draft"
    assert exc_info.value.target_state == "completed"
    assert "active" in exc_info.value.valid_transitions


# ── 6. Test ensure_modifiable in draft/active → ok ──────────────────────


def test_ensure_modifiable_draft_ok():
    e = _entity("draft")
    e.ensure_modifiable()  # no raise


def test_ensure_modifiable_active_ok():
    e = _entity("active", test_count=1)
    e.ensure_modifiable()  # no raise


# ── 7. Test ensure_modifiable in completed → BusinessRuleViolation ──────


def test_ensure_modifiable_completed_raises():
    e = _entity("completed", test_count=1)
    with pytest.raises(BusinessRuleViolation, match="Cannot modify"):
        e.ensure_modifiable()


def test_ensure_modifiable_archived_raises():
    e = _entity("archived", test_count=1)
    with pytest.raises(BusinessRuleViolation, match="Cannot modify"):
        e.ensure_modifiable()


# ── 8. Test from_orm conversion ──────────────────────────────────────────


def test_from_orm_basic():
    orm = _fake_orm("draft", test_count=0)
    e = CampaignEntity.from_orm(orm)
    assert e.name == "Test Campaign"
    assert e.type == CampaignType.custom
    assert e.status == CampaignStatus.draft
    assert e.id == orm.id
    assert e.test_count == 0


def test_from_orm_with_tests():
    orm = _fake_orm("draft", test_count=3)
    e = CampaignEntity.from_orm(orm)
    assert e.test_count == 3


def test_from_orm_coerces_type_and_status():
    orm = _fake_orm(status="active", type="apt_emulation", test_count=1)
    e = CampaignEntity.from_orm(orm)
    assert e.status == CampaignStatus.active
    assert e.type == CampaignType.apt_emulation


def test_from_orm_handles_none_tags():
    orm = _fake_orm("draft", test_count=0)
    orm.tags = None
    e = CampaignEntity.from_orm(orm)
    assert e.tags == []


# ── 9. Submit for approval ────────────────────────────────────────────


def test_submit_for_approval_from_draft_with_tests_success():
    e = _entity("draft", test_count=1)
    e.submit_for_approval()
    assert e.status == CampaignStatus.pending_approval


def test_submit_for_approval_from_draft_with_zero_tests_raises():
    e = _entity("draft", test_count=0)
    with pytest.raises(BusinessRuleViolation, match="at least one test"):
        e.submit_for_approval()
    assert e.status == CampaignStatus.draft


def test_submit_for_approval_from_active_raises():
    e = _entity("active", test_count=1)
    with pytest.raises(InvalidStateTransition):
        e.submit_for_approval()


# ── 10. Approve ────────────────────────────────────────────────────────


def test_approve_from_pending_approval_success():
    e = _entity("pending_approval", test_count=1)
    e.approve()
    assert e.status == CampaignStatus.active


def test_approve_from_draft_raises():
    e = _entity("draft", test_count=1)
    with pytest.raises(InvalidStateTransition):
        e.approve()


def test_approve_from_completed_raises():
    e = _entity("completed", test_count=1)
    with pytest.raises(InvalidStateTransition):
        e.approve()


# ── 11. Reject ─────────────────────────────────────────────────────────


def test_reject_from_pending_approval_returns_to_draft():
    e = _entity("pending_approval", test_count=1)
    e.reject()
    assert e.status == CampaignStatus.draft


def test_reject_from_draft_raises():
    e = _entity("draft", test_count=1)
    with pytest.raises(InvalidStateTransition):
        e.reject()
