"""
Unit Tests - Domain Entities.

تست‌های واحد موجودیت‌های دامنه ماژول HRM.
"""
import pytest
from datetime import date
from uuid import uuid4

from modules.hrm.backend.domain.entities.employee import (
    Employee, EmploymentStatus, Gender, MaritalStatus,
)
from modules.hrm.backend.domain.entities.position import (
    Position, PositionStatus,
)
from modules.hrm.backend.domain.entities.leave import (
    LeaveRequest, LeaveRequestStatus, LeaveBalance,
)
from modules.hrm.backend.domain.entities.time_attendance import (
    Timesheet, TimesheetStatus,
)
from modules.hrm.backend.domain.entities.payroll import PayGrade
from modules.hrm.backend.domain.value_objects.common import (
    NationalCode, ShebaNumber, Money, DateRange,
)


# ═══════════════════════════════════════════════════
# Employee Tests
# ═══════════════════════════════════════════════════

class TestEmployee:

    def _make_employee(self, **overrides):
        defaults = {
            "id": uuid4(),
            "tenant_id": uuid4(),
            "employee_code": "EMP001",
            "first_name": "علی",
            "last_name": "احمدی",
            "national_code": "0012345678",
            "hire_date": date(2024, 1, 1),
            "employment_status": EmploymentStatus.ACTIVE,
        }
        defaults.update(overrides)
        return Employee(**defaults)

    def test_full_name(self):
        emp = self._make_employee()
        assert emp.full_name == "علی احمدی"

    def test_full_name_en(self):
        emp = self._make_employee(first_name_en="Ali", last_name_en="Ahmadi")
        assert emp.full_name_en == "Ali Ahmadi"

    def test_validate_valid(self):
        emp = self._make_employee()
        emp.validate()  # Should not raise

    def test_validate_missing_code(self):
        emp = self._make_employee(employee_code="")
        with pytest.raises(ValueError, match="کد پرسنلی"):
            emp.validate()

    def test_validate_invalid_national_code(self):
        emp = self._make_employee(national_code="123")
        with pytest.raises(ValueError, match="کد ملی"):
            emp.validate()

    def test_terminate_active(self):
        emp = self._make_employee(employment_status=EmploymentStatus.ACTIVE)
        emp.terminate(date(2024, 12, 31))
        assert emp.employment_status == EmploymentStatus.TERMINATED
        assert emp.termination_date == date(2024, 12, 31)
        assert emp.is_active is False

    def test_terminate_already_terminated(self):
        emp = self._make_employee(employment_status=EmploymentStatus.TERMINATED)
        with pytest.raises(ValueError, match="خاتمه همکاری"):
            emp.terminate(date(2024, 12, 31))

    def test_activate_from_probation(self):
        emp = self._make_employee(employment_status=EmploymentStatus.PROBATION)
        emp.activate()
        assert emp.employment_status == EmploymentStatus.ACTIVE

    def test_activate_from_active_fails(self):
        emp = self._make_employee(employment_status=EmploymentStatus.ACTIVE)
        with pytest.raises(ValueError, match="دوره آزمایشی"):
            emp.activate()

    def test_transfer(self):
        new_unit = uuid4()
        new_pos = uuid4()
        emp = self._make_employee()
        emp.transfer(new_unit, new_pos)
        assert emp.org_unit_id == new_unit
        assert emp.primary_position_id == new_pos

    def test_transfer_terminated_fails(self):
        emp = self._make_employee(employment_status=EmploymentStatus.TERMINATED)
        with pytest.raises(ValueError, match="خاتمه‌یافته"):
            emp.transfer(uuid4())

    def test_suspend(self):
        emp = self._make_employee(employment_status=EmploymentStatus.ACTIVE)
        emp.suspend()
        assert emp.employment_status == EmploymentStatus.SUSPENDED


# ═══════════════════════════════════════════════════
# Position Tests
# ═══════════════════════════════════════════════════

class TestPosition:

    def _make_position(self, **overrides):
        defaults = {
            "id": uuid4(),
            "tenant_id": uuid4(),
            "code": "POS001",
            "title": "مدیر فنی",
            "job_title_id": uuid4(),
            "org_unit_id": uuid4(),
            "status": PositionStatus.ACTIVE,
        }
        defaults.update(overrides)
        return Position(**defaults)

    def test_fill_position(self):
        pos = self._make_position()
        emp_id = uuid4()
        pos.fill(emp_id)
        assert pos.incumbent_id == emp_id
        assert not pos.is_vacant

    def test_fill_already_filled(self):
        pos = self._make_position(incumbent_id=uuid4())
        with pytest.raises(ValueError):
            pos.fill(uuid4())

    def test_vacate_position(self):
        emp_id = uuid4()
        pos = self._make_position(incumbent_id=emp_id)
        pos.vacate()
        assert pos.incumbent_id is None
        assert pos.is_vacant

    def test_freeze_position(self):
        pos = self._make_position()
        pos.freeze()
        assert pos.status == PositionStatus.FROZEN

    def test_abolish_position(self):
        pos = self._make_position()
        pos.abolish()
        assert pos.status == PositionStatus.ABOLISHED


# ═══════════════════════════════════════════════════
# Leave Tests
# ═══════════════════════════════════════════════════

class TestLeaveRequest:

    def _make_leave_request(self, **overrides):
        defaults = {
            "id": uuid4(),
            "tenant_id": uuid4(),
            "employee_id": uuid4(),
            "leave_type": "ANNUAL",
            "start_date": date(2024, 6, 1),
            "end_date": date(2024, 6, 3),
            "days_count": 3,
            "status": LeaveRequestStatus.DRAFT,
        }
        defaults.update(overrides)
        return LeaveRequest(**defaults)

    def test_submit(self):
        lr = self._make_leave_request()
        lr.submit()
        assert lr.status == LeaveRequestStatus.PENDING

    def test_approve(self):
        approver = uuid4()
        lr = self._make_leave_request(status=LeaveRequestStatus.PENDING)
        lr.approve(approver)
        assert lr.status == LeaveRequestStatus.APPROVED
        assert lr.approved_by == approver

    def test_reject(self):
        lr = self._make_leave_request(status=LeaveRequestStatus.PENDING)
        lr.reject("دلیل رد")
        assert lr.status == LeaveRequestStatus.REJECTED

    def test_cancel_draft(self):
        lr = self._make_leave_request(status=LeaveRequestStatus.DRAFT)
        lr.cancel()
        assert lr.status == LeaveRequestStatus.CANCELLED


class TestLeaveBalance:

    def test_available_days(self):
        lb = LeaveBalance(
            id=uuid4(),
            tenant_id=uuid4(),
            employee_id=uuid4(),
            leave_type="ANNUAL",
            year=2024,
            entitled_days=26,
            carried_over_days=5,
            used_days=10,
            pending_days=2,
            adjustment_days=1,
        )
        assert lb.available_days == 20  # 26+5+1-10-2

    def test_reserve(self):
        lb = LeaveBalance(
            id=uuid4(),
            tenant_id=uuid4(),
            employee_id=uuid4(),
            leave_type="ANNUAL",
            year=2024,
            entitled_days=26,
            used_days=0,
            pending_days=0,
        )
        lb.reserve(3)
        assert lb.pending_days == 3

    def test_confirm(self):
        lb = LeaveBalance(
            id=uuid4(),
            tenant_id=uuid4(),
            employee_id=uuid4(),
            leave_type="ANNUAL",
            year=2024,
            entitled_days=26,
            used_days=0,
            pending_days=5,
        )
        lb.confirm(3)
        assert lb.used_days == 3
        assert lb.pending_days == 2


# ═══════════════════════════════════════════════════
# Timesheet Tests
# ═══════════════════════════════════════════════════

class TestTimesheet:

    def test_submit(self):
        ts = Timesheet(
            id=uuid4(),
            tenant_id=uuid4(),
            employee_id=uuid4(),
            period_start=date(2024, 1, 1),
            period_end=date(2024, 1, 31),
            status=TimesheetStatus.DRAFT,
        )
        ts.submit()
        assert ts.status == TimesheetStatus.SUBMITTED

    def test_approve(self):
        approver = uuid4()
        ts = Timesheet(
            id=uuid4(),
            tenant_id=uuid4(),
            employee_id=uuid4(),
            period_start=date(2024, 1, 1),
            period_end=date(2024, 1, 31),
            status=TimesheetStatus.SUBMITTED,
        )
        ts.approve(approver)
        assert ts.status == TimesheetStatus.APPROVED


# ═══════════════════════════════════════════════════
# Value Object Tests
# ═══════════════════════════════════════════════════

class TestNationalCode:

    def test_valid_code(self):
        nc = NationalCode("0012345678")
        assert nc.value == "0012345678"

    def test_invalid_length(self):
        with pytest.raises(ValueError):
            NationalCode("123")


class TestShebaNumber:

    def test_valid_sheba(self):
        sheba = ShebaNumber("IR012345678901234567890123")
        assert sheba.value.startswith("IR")

    def test_invalid_prefix(self):
        with pytest.raises(ValueError):
            ShebaNumber("US012345678901234567890123")


class TestMoney:

    def test_add(self):
        m1 = Money(amount=1000, currency="IRR")
        m2 = Money(amount=2000, currency="IRR")
        result = m1.add(m2)
        assert result.amount == 3000

    def test_add_different_currency(self):
        m1 = Money(amount=1000, currency="IRR")
        m2 = Money(amount=2000, currency="USD")
        with pytest.raises(ValueError):
            m1.add(m2)


class TestDateRange:

    def test_days(self):
        dr = DateRange(start=date(2024, 1, 1), end=date(2024, 1, 10))
        assert dr.days == 9

    def test_overlaps(self):
        dr1 = DateRange(start=date(2024, 1, 1), end=date(2024, 1, 10))
        dr2 = DateRange(start=date(2024, 1, 5), end=date(2024, 1, 15))
        assert dr1.overlaps(dr2) is True

    def test_no_overlap(self):
        dr1 = DateRange(start=date(2024, 1, 1), end=date(2024, 1, 10))
        dr2 = DateRange(start=date(2024, 1, 15), end=date(2024, 1, 20))
        assert dr1.overlaps(dr2) is False


# ═══════════════════════════════════════════════════
# PayGrade Tests
# ═══════════════════════════════════════════════════

class TestPayGrade:

    def test_step_amount(self):
        pg = PayGrade(
            id=uuid4(),
            tenant_id=uuid4(),
            name="Grade A",
            code="GA",
            min_amount=50_000_000,
            mid_amount=75_000_000,
            max_amount=100_000_000,
            step_count=5,
            step_increment=10_000_000,
        )
        assert pg.step_amount(1) == 50_000_000
        assert pg.step_amount(2) == 60_000_000
        assert pg.step_amount(5) == 90_000_000
