"""
Unit Tests - Application Use Cases.

تست‌های واحد یوزکیس‌های لایه اپلیکیشن ماژول HRM.
"""
import pytest
from datetime import date
from unittest.mock import MagicMock, AsyncMock, patch
from uuid import uuid4

from modules.hrm.backend.domain.entities.employee import (
    Employee, EmploymentStatus,
)
from modules.hrm.backend.domain.entities.leave import (
    LeaveRequest, LeaveRequestStatus, LeaveBalance,
)
from modules.hrm.backend.domain.entities.position import (
    Position, PositionStatus,
)
from modules.hrm.backend.application.use_cases.employee_use_cases import (
    HireEmployeeUseCase,
    TerminateEmployeeUseCase,
    TransferEmployeeUseCase,
)
from modules.hrm.backend.application.use_cases.leave_use_cases import (
    RequestLeaveUseCase,
    ApproveLeaveUseCase,
    RejectLeaveUseCase,
)
from modules.hrm.backend.application.use_cases.position_use_cases import (
    CreatePositionUseCase,
    FillPositionUseCase,
)
from modules.hrm.backend.application.dtos.hrm_dtos import (
    HireEmployeeDTO,
    TerminateEmployeeDTO,
    TransferEmployeeDTO,
    CreateLeaveRequestDTO,
    CreatePositionDTO,
)


# ═══════════════════════════════════════════════════
# Employee Use Cases
# ═══════════════════════════════════════════════════

class TestHireEmployeeUseCase:

    def setup_method(self):
        self.repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = HireEmployeeUseCase(
            employee_repo=self.repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_hire_success(self):
        dto = HireEmployeeDTO(
            employee_code="EMP100",
            first_name="رضا",
            last_name="کریمی",
            national_code="0012345678",
            hire_date=date(2024, 3, 1),
        )
        self.repo.find_by_code.return_value = None
        self.repo.save.return_value = None

        result = self.use_case.execute(self.tenant_id, dto)

        self.repo.save.assert_called_once()
        assert result is not None

    def test_hire_duplicate_code(self):
        dto = HireEmployeeDTO(
            employee_code="EMP100",
            first_name="رضا",
            last_name="کریمی",
            national_code="0012345678",
            hire_date=date(2024, 3, 1),
        )
        existing = MagicMock()
        self.repo.find_by_code.return_value = existing

        with pytest.raises(Exception):
            self.use_case.execute(self.tenant_id, dto)


class TestTerminateEmployeeUseCase:

    def setup_method(self):
        self.repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = TerminateEmployeeUseCase(
            employee_repo=self.repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_terminate_success(self):
        emp_id = uuid4()
        employee = Employee(
            id=emp_id,
            tenant_id=self.tenant_id,
            employee_code="EMP001",
            first_name="علی",
            last_name="احمدی",
            national_code="0012345678",
            hire_date=date(2024, 1, 1),
            employment_status=EmploymentStatus.ACTIVE,
        )
        self.repo.get_by_id.return_value = employee

        dto = TerminateEmployeeDTO(
            employee_id=emp_id,
            termination_date=date(2024, 12, 31),
            reason="استعفا",
        )
        self.use_case.execute(self.tenant_id, dto)

        self.repo.save.assert_called_once()

    def test_terminate_not_found(self):
        self.repo.get_by_id.return_value = None
        dto = TerminateEmployeeDTO(
            employee_id=uuid4(),
            termination_date=date(2024, 12, 31),
            reason="استعفا",
        )
        with pytest.raises(Exception):
            self.use_case.execute(self.tenant_id, dto)


class TestTransferEmployeeUseCase:

    def setup_method(self):
        self.repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = TransferEmployeeUseCase(
            employee_repo=self.repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_transfer_success(self):
        emp_id = uuid4()
        new_unit = uuid4()
        employee = Employee(
            id=emp_id,
            tenant_id=self.tenant_id,
            employee_code="EMP001",
            first_name="علی",
            last_name="احمدی",
            national_code="0012345678",
            hire_date=date(2024, 1, 1),
            employment_status=EmploymentStatus.ACTIVE,
        )
        self.repo.get_by_id.return_value = employee

        dto = TransferEmployeeDTO(
            employee_id=emp_id,
            new_org_unit_id=new_unit,
            effective_date=date(2024, 6, 1),
        )
        self.use_case.execute(self.tenant_id, dto)

        self.repo.save.assert_called_once()


# ═══════════════════════════════════════════════════
# Leave Use Cases
# ═══════════════════════════════════════════════════

class TestRequestLeaveUseCase:

    def setup_method(self):
        self.leave_repo = MagicMock()
        self.balance_repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = RequestLeaveUseCase(
            leave_request_repo=self.leave_repo,
            leave_balance_repo=self.balance_repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_request_leave_success(self):
        emp_id = uuid4()
        balance = LeaveBalance(
            id=uuid4(),
            tenant_id=self.tenant_id,
            employee_id=emp_id,
            leave_type="ANNUAL",
            year=2024,
            entitled_days=26,
            used_days=5,
            pending_days=0,
        )
        self.balance_repo.find_by_employee_and_type.return_value = balance

        dto = CreateLeaveRequestDTO(
            employee_id=emp_id,
            leave_type="ANNUAL",
            start_date=date(2024, 6, 1),
            end_date=date(2024, 6, 3),
            days_count=3,
        )
        self.use_case.execute(self.tenant_id, dto)

        self.leave_repo.save.assert_called_once()


class TestApproveLeaveUseCase:

    def setup_method(self):
        self.leave_repo = MagicMock()
        self.balance_repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = ApproveLeaveUseCase(
            leave_request_repo=self.leave_repo,
            leave_balance_repo=self.balance_repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_approve_success(self):
        request_id = uuid4()
        approver_id = uuid4()
        emp_id = uuid4()
        lr = LeaveRequest(
            id=request_id,
            tenant_id=self.tenant_id,
            employee_id=emp_id,
            leave_type="ANNUAL",
            start_date=date(2024, 6, 1),
            end_date=date(2024, 6, 3),
            days_count=3,
            status=LeaveRequestStatus.PENDING,
        )
        self.leave_repo.get_by_id.return_value = lr

        balance = LeaveBalance(
            id=uuid4(),
            tenant_id=self.tenant_id,
            employee_id=emp_id,
            leave_type="ANNUAL",
            year=2024,
            entitled_days=26,
            used_days=5,
            pending_days=3,
        )
        self.balance_repo.find_by_employee_and_type.return_value = balance

        self.use_case.execute(self.tenant_id, request_id, approver_id)

        self.leave_repo.save.assert_called_once()


# ═══════════════════════════════════════════════════
# Position Use Cases
# ═══════════════════════════════════════════════════

class TestCreatePositionUseCase:

    def setup_method(self):
        self.repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = CreatePositionUseCase(
            position_repo=self.repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_create_success(self):
        dto = CreatePositionDTO(
            code="POS001",
            title="مدیر فنی",
            job_title_id=uuid4(),
            org_unit_id=uuid4(),
        )
        self.repo.find_by_code.return_value = None
        self.use_case.execute(self.tenant_id, dto)

        self.repo.save.assert_called_once()


class TestFillPositionUseCase:

    def setup_method(self):
        self.position_repo = MagicMock()
        self.employee_repo = MagicMock()
        self.publisher = MagicMock()
        self.use_case = FillPositionUseCase(
            position_repo=self.position_repo,
            employee_repo=self.employee_repo,
            event_publisher=self.publisher,
        )
        self.tenant_id = uuid4()

    def test_fill_success(self):
        pos_id = uuid4()
        emp_id = uuid4()
        position = Position(
            id=pos_id,
            tenant_id=self.tenant_id,
            code="POS001",
            title="مدیر فنی",
            job_title_id=uuid4(),
            org_unit_id=uuid4(),
            status=PositionStatus.ACTIVE,
        )
        employee = Employee(
            id=emp_id,
            tenant_id=self.tenant_id,
            employee_code="EMP001",
            first_name="علی",
            last_name="احمدی",
            national_code="0012345678",
            hire_date=date(2024, 1, 1),
            employment_status=EmploymentStatus.ACTIVE,
        )
        self.position_repo.get_by_id.return_value = position
        self.employee_repo.get_by_id.return_value = employee

        self.use_case.execute(self.tenant_id, pos_id, emp_id)

        self.position_repo.save.assert_called_once()
