"""
PM Module — Domain Unit Tests for Scenario 1.

تست‌های واحد لایه Domain برای سناریوی مدیریت پروژه.
"""
import pytest
from datetime import date
from decimal import Decimal
from uuid import uuid4

from modules.pm.backend.domain.entities.project import Project
from modules.pm.backend.domain.entities.task import Task, Dependency, TaskAssignment
from modules.pm.backend.domain.value_objects.common import (
    ProjectStatus,
    TaskStatus,
    DependencyType,
)


# ═══════════════════════════════════════════════════════════
# 1) Task Entity — Business Rules
# ═══════════════════════════════════════════════════════════

class TestTaskEntity:
    """تست‌های واحد Entity تسک."""

    def _make_task(self, **overrides):
        defaults = dict(
            id=uuid4(), tenant_id=uuid4(), project_id=uuid4(),
            code="T-001", title="تسک تست",
            status=TaskStatus.NOT_STARTED, progress=0, duration=10,
        )
        defaults.update(overrides)
        return Task(**defaults)

    def test_start_work(self):
        task = self._make_task()
        task.start_work()
        assert task.status == TaskStatus.IN_PROGRESS
        assert task.actual_start == date.today()

    def test_start_work_fails_if_already_started(self):
        task = self._make_task(status=TaskStatus.IN_PROGRESS)
        with pytest.raises(ValueError, match="قبلاً شروع شده"):
            task.start_work()

    def test_complete_task(self):
        task = self._make_task(status=TaskStatus.IN_PROGRESS)
        task.complete()
        assert task.status == TaskStatus.COMPLETED
        assert task.progress == 100
        assert task.actual_end == date.today()

    def test_complete_fails_if_already_completed(self):
        task = self._make_task(status=TaskStatus.COMPLETED)
        with pytest.raises(ValueError, match="قابل تکمیل نیست"):
            task.complete()

    def test_update_progress(self):
        task = self._make_task()
        task.update_progress(50)
        assert task.progress == 50
        assert task.status == TaskStatus.IN_PROGRESS

    def test_update_progress_to_100_completes(self):
        task = self._make_task(status=TaskStatus.IN_PROGRESS)
        task.update_progress(100)
        assert task.status == TaskStatus.COMPLETED

    def test_update_progress_invalid(self):
        task = self._make_task()
        with pytest.raises(ValueError, match="بین ۰ تا ۱۰۰"):
            task.update_progress(150)

    def test_hold_task(self):
        task = self._make_task(status=TaskStatus.IN_PROGRESS)
        task.hold()
        assert task.status == TaskStatus.ON_HOLD

    def test_resume_task(self):
        task = self._make_task(status=TaskStatus.ON_HOLD)
        task.resume()
        assert task.status == TaskStatus.IN_PROGRESS

    def test_cancel_task(self):
        task = self._make_task(status=TaskStatus.IN_PROGRESS)
        task.cancel()
        assert task.status == TaskStatus.CANCELLED

    def test_cancel_fails_if_completed(self):
        task = self._make_task(status=TaskStatus.COMPLETED)
        with pytest.raises(ValueError, match="قابل لغو نیست"):
            task.cancel()

    def test_is_overdue(self):
        from datetime import timedelta
        task = self._make_task(
            status=TaskStatus.IN_PROGRESS,
            planned_end=date.today() - timedelta(days=1),
        )
        assert task.is_overdue is True

    def test_not_overdue_if_completed(self):
        from datetime import timedelta
        task = self._make_task(
            status=TaskStatus.COMPLETED,
            planned_end=date.today() - timedelta(days=1),
        )
        assert task.is_overdue is False

    def test_cost_variance(self):
        task = self._make_task(
            planned_cost=Decimal("100"),
            actual_cost=Decimal("80"),
        )
        assert task.cost_variance == Decimal("20")


# ═══════════════════════════════════════════════════════════
# 2) Project Entity — Business Rules
# ═══════════════════════════════════════════════════════════

class TestProjectEntity:
    """تست‌های واحد Entity پروژه."""

    def _make_project(self, **overrides):
        defaults = dict(
            id=uuid4(), tenant_id=uuid4(),
            code="PRJ-001", title="پروژه تست",
            status=ProjectStatus.DRAFT, progress=0,
            planned_start=date.today(),
        )
        defaults.update(overrides)
        return Project(**defaults)

    def test_cannot_start_draft_project(self):
        project = self._make_project(status=ProjectStatus.DRAFT)
        assert project.can_start() is False

    def test_can_start_planning_project(self):
        project = self._make_project(status=ProjectStatus.PLANNING)
        assert project.can_start() is True

    def test_start_project(self):
        project = self._make_project(status=ProjectStatus.PLANNING)
        project.start()
        assert project.status == ProjectStatus.ACTIVE
        assert project.actual_start == date.today()

    def test_complete_project(self):
        project = self._make_project(status=ProjectStatus.ACTIVE)
        project.complete()
        assert project.status == ProjectStatus.COMPLETED
        assert project.progress == 100

    def test_cancel_project(self):
        project = self._make_project(status=ProjectStatus.ACTIVE)
        project.cancel()
        assert project.status == ProjectStatus.CANCELLED

    def test_cancel_fails_if_completed(self):
        project = self._make_project(status=ProjectStatus.COMPLETED)
        with pytest.raises(ValueError, match="قابل لغو نیست"):
            project.cancel()

    def test_update_progress(self):
        project = self._make_project(status=ProjectStatus.ACTIVE)
        project.update_progress(60)
        assert project.progress == 60

    def test_update_progress_invalid(self):
        project = self._make_project()
        with pytest.raises(ValueError, match="بین ۰ تا ۱۰۰"):
            project.update_progress(-5)


# ═══════════════════════════════════════════════════════════
# 3) Dependency Entity — Validation
# ═══════════════════════════════════════════════════════════

class TestDependencyEntity:
    """تست‌های واحد Entity وابستگی."""

    def test_self_dependency_fails(self):
        task_id = uuid4()
        dep = Dependency(
            id=uuid4(), tenant_id=uuid4(),
            project_id=uuid4(),
            predecessor_id=task_id,
            successor_id=task_id,
        )
        with pytest.raises(ValueError, match="نمی‌تواند به خودش وابسته"):
            dep.validate()

    def test_valid_dependency(self):
        dep = Dependency(
            id=uuid4(), tenant_id=uuid4(),
            project_id=uuid4(),
            predecessor_id=uuid4(),
            successor_id=uuid4(),
            dependency_type=DependencyType.FS,
        )
        dep.validate()  # Should not raise


# ═══════════════════════════════════════════════════════════
# 4) Exceptions
# ═══════════════════════════════════════════════════════════

class TestExceptions:
    """تست‌های استثناها."""

    def test_dependency_not_satisfied(self):
        from modules.pm.backend.domain.exceptions.pm_exceptions import (
            DependencyNotSatisfiedError,
        )
        exc = DependencyNotSatisfiedError("تحلیل نیازمندی‌ها")
        assert "تحلیل نیازمندی‌ها" in str(exc)

    def test_task_has_dependents(self):
        from modules.pm.backend.domain.exceptions.pm_exceptions import (
            TaskHasDependentsError,
        )
        exc = TaskHasDependentsError("طراحی معماری")
        assert "طراحی معماری" in str(exc)
