"""
HRM Module — Model Unit Tests.

تست‌های واحد برای ۲۰ مدل ORM ماژول منابع انسانی.
"""
import uuid
from datetime import date, time
from decimal import Decimal

import pytest
from django.db import IntegrityError

from modules.hrm.backend.infrastructure.persistence.models import (
    LegalEntityModel,
    LocationModel,
    EmployeeModel,
    EmploymentRecordModel,
    EmployeeDocumentModel,
    JobFamilyModel,
    JobTitleModel,
    PositionModel,
    ReportingLineModel,
)
from modules.hrm.backend.infrastructure.persistence.models_extended import (
    WorkScheduleModel,
    ShiftPatternModel,
    TimesheetModel,
    AttendanceRecordModel,
    LeavePolicyModel,
    LeaveBalanceModel,
    LeaveRequestModel,
    PayGradeModel,
    CompensationRecordModel,
    BankAccountModel,
    CostAllocationModel,
)

pytestmark = pytest.mark.django_db


# ═══════════════════════════════════════════════
# 1) Organization Models
# ═══════════════════════════════════════════════

class TestLegalEntityModel:
    def test_create(self, legal_entity):
        assert legal_entity.pk is not None
        assert legal_entity.name == "شرکت آزمایشی"
        assert legal_entity.is_active is True

    def test_str(self, legal_entity):
        assert str(legal_entity) == "شرکت آزمایشی"

    def test_entity_types(self):
        types = [c[0] for c in LegalEntityModel.EntityType.choices]
        assert "COMPANY" in types
        assert "BRANCH" in types
        assert "REPRESENTATIVE" in types

    def test_unique_national_id_per_tenant(self, tenant, legal_entity):
        with pytest.raises(IntegrityError):
            LegalEntityModel.objects.create(
                id=uuid.uuid4(),
                tenant=tenant,
                name="Duplicate",
                registration_number="99999",
                national_id=legal_entity.national_id,
            )

    def test_parent_entity(self, tenant, legal_entity):
        branch = LegalEntityModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            name="شعبه ۱",
            registration_number="67890",
            national_id="20202020202",
            entity_type=LegalEntityModel.EntityType.BRANCH,
            parent_entity=legal_entity,
        )
        assert branch.parent_entity == legal_entity
        assert legal_entity.children.count() == 1


class TestLocationModel:
    def test_create(self, location):
        assert location.pk is not None
        assert location.code == "HQ-01"

    def test_str(self, location):
        assert "HQ-01" in str(location)

    def test_location_types(self):
        types = [c[0] for c in LocationModel.LocationType.choices]
        assert "HEADQUARTERS" in types
        assert "REMOTE" in types

    def test_unique_code_per_tenant(self, tenant, location):
        with pytest.raises(IntegrityError):
            LocationModel.objects.create(
                id=uuid.uuid4(),
                tenant=tenant,
                name="Duplicate",
                code=location.code,
            )


# ═══════════════════════════════════════════════
# 2) Employee Models
# ═══════════════════════════════════════════════

class TestEmployeeModel:
    def test_create(self, employee):
        assert employee.pk is not None
        assert employee.employee_code == "EMP-001"
        assert employee.employment_status == "ACTIVE"

    def test_str(self, employee):
        assert "علی" in str(employee)
        assert "EMP-001" in str(employee)

    def test_full_name(self, employee):
        assert employee.full_name == "علی محمدی"

    def test_unique_employee_code_per_tenant(self, tenant, employee):
        with pytest.raises(IntegrityError):
            EmployeeModel.objects.create(
                id=uuid.uuid4(),
                tenant=tenant,
                employee_code=employee.employee_code,
                first_name="X",
                last_name="Y",
                national_code="9999999999",
            )

    def test_unique_national_code_per_tenant(self, tenant, employee):
        with pytest.raises(IntegrityError):
            EmployeeModel.objects.create(
                id=uuid.uuid4(),
                tenant=tenant,
                employee_code="EMP-999",
                first_name="X",
                last_name="Y",
                national_code=employee.national_code,
            )

    def test_manager_relation(self, tenant, employee, employee_2):
        employee_2.manager = employee
        employee_2.save()
        assert employee.direct_reports.count() == 1
        assert employee.direct_reports.first() == employee_2


class TestEmploymentRecordModel:
    def test_create(self, tenant, employee):
        record = EmploymentRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            contract_type=EmploymentRecordModel.ContractType.PERMANENT,
            start_date=date(2023, 1, 15),
            is_current=True,
        )
        assert record.pk is not None
        assert record.employee == employee

    def test_str(self, tenant, employee):
        record = EmploymentRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            start_date=date(2023, 1, 15),
        )
        assert str(employee) in str(record)


class TestEmployeeDocumentModel:
    def test_create(self, tenant, employee):
        doc = EmployeeDocumentModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            document_type="ID_CARD",
            title="کارت ملی",
        )
        assert doc.pk is not None
        assert doc.is_verified is False


# ═══════════════════════════════════════════════
# 3) Job / Position Models
# ═══════════════════════════════════════════════

class TestJobFamilyModel:
    def test_create(self, job_family):
        assert job_family.pk is not None
        assert job_family.code == "IT"

    def test_str(self, job_family):
        assert str(job_family) == "فناوری اطلاعات"


class TestJobTitleModel:
    def test_create(self, job_title):
        assert job_title.pk is not None
        assert job_title.level == "SENIOR"

    def test_str(self, job_title):
        assert "SE-01" in str(job_title)


class TestPositionModel:
    def test_create(self, position):
        assert position.pk is not None
        assert position.status == "ACTIVE"

    def test_str(self, position):
        assert "POS-001" in str(position)

    def test_is_vacant_with_incumbent(self, position):
        assert position.is_vacant is False

    def test_is_vacant_without_incumbent(self, tenant, job_title):
        vacant_pos = PositionModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            title="پست خالی",
            code="POS-VACANT",
            job_title=job_title,
        )
        assert vacant_pos.is_vacant is True


class TestReportingLineModel:
    def test_create(self, tenant, employee, employee_2):
        line = ReportingLineModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee_2,
            manager=employee,
            reporting_type=ReportingLineModel.ReportingType.DIRECT,
            is_primary=True,
        )
        assert line.pk is not None
        assert line.employee == employee_2
        assert line.manager == employee


# ═══════════════════════════════════════════════
# 4) Time & Attendance Models
# ═══════════════════════════════════════════════

class TestWorkScheduleModel:
    def test_create(self, work_schedule):
        assert work_schedule.pk is not None
        assert work_schedule.daily_hours == Decimal("8.00")

    def test_str(self, work_schedule):
        assert "WS-STD" in str(work_schedule)


class TestShiftPatternModel:
    def test_create(self, shift_pattern):
        assert shift_pattern.pk is not None
        assert shift_pattern.cycle_days == 14

    def test_str(self, shift_pattern):
        assert str(shift_pattern) == "شیفت گردشی"


class TestTimesheetModel:
    def test_create(self, tenant, employee):
        ts = TimesheetModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            period_start=date(2024, 1, 1),
            period_end=date(2024, 1, 31),
            status=TimesheetModel.TimesheetStatus.DRAFT,
        )
        assert ts.pk is not None
        assert ts.status == "DRAFT"


class TestAttendanceRecordModel:
    def test_create(self, tenant, employee):
        ar = AttendanceRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            attendance_date=date(2024, 1, 15),
            status=AttendanceRecordModel.AttendanceStatus.PRESENT,
            effective_hours=Decimal("8.00"),
        )
        assert ar.pk is not None

    def test_unique_per_day(self, tenant, employee):
        AttendanceRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            attendance_date=date(2024, 2, 1),
        )
        with pytest.raises(IntegrityError):
            AttendanceRecordModel.objects.create(
                id=uuid.uuid4(),
                tenant=tenant,
                employee=employee,
                attendance_date=date(2024, 2, 1),
            )


# ═══════════════════════════════════════════════
# 5) Leave Models
# ═══════════════════════════════════════════════

class TestLeavePolicyModel:
    def test_create(self, leave_policy):
        assert leave_policy.pk is not None
        assert leave_policy.annual_entitlement_days == Decimal("26.00")

    def test_str(self, leave_policy):
        assert "ANNUAL" in str(leave_policy)


class TestLeaveBalanceModel:
    def test_create(self, tenant, employee, leave_policy):
        lb = LeaveBalanceModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            leave_policy=leave_policy,
            leave_type="ANNUAL",
            year=2024,
            entitled_days=Decimal("26.00"),
            used_days=Decimal("5.00"),
        )
        assert lb.pk is not None

    def test_available_days(self, tenant, employee, leave_policy):
        lb = LeaveBalanceModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            leave_policy=leave_policy,
            leave_type="ANNUAL",
            year=2024,
            entitled_days=Decimal("26.00"),
            carried_over_days=Decimal("4.00"),
            used_days=Decimal("10.00"),
            pending_days=Decimal("2.00"),
            adjustment_days=Decimal("1.00"),
        )
        assert lb.available_days == Decimal("19.00")  # 26+4+1-10-2


class TestLeaveRequestModel:
    def test_create(self, tenant, employee):
        lr = LeaveRequestModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            leave_type="ANNUAL",
            start_date=date(2024, 3, 1),
            end_date=date(2024, 3, 3),
            days_count=Decimal("3.00"),
            status=LeaveRequestModel.LeaveRequestStatus.PENDING,
        )
        assert lr.pk is not None
        assert lr.status == "PENDING"


# ═══════════════════════════════════════════════
# 6) Payroll Models
# ═══════════════════════════════════════════════

class TestPayGradeModel:
    def test_create(self, pay_grade):
        assert pay_grade.pk is not None
        assert pay_grade.min_amount == Decimal("50000000")

    def test_str(self, pay_grade):
        assert "PG-05" in str(pay_grade)


class TestCompensationRecordModel:
    def test_create(self, tenant, employee, pay_grade):
        cr = CompensationRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            pay_grade=pay_grade,
            base_salary=Decimal("60000000"),
            housing_allowance=Decimal("5000000"),
            transportation_allowance=Decimal("3000000"),
            food_allowance=Decimal("2000000"),
            family_allowance=Decimal("1000000"),
            effective_date=date(2024, 1, 1),
            is_current=True,
        )
        assert cr.pk is not None

    def test_total_monthly(self, tenant, employee, pay_grade):
        cr = CompensationRecordModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            pay_grade=pay_grade,
            base_salary=Decimal("60000000"),
            housing_allowance=Decimal("5000000"),
            transportation_allowance=Decimal("3000000"),
            food_allowance=Decimal("2000000"),
            family_allowance=Decimal("1000000"),
            effective_date=date(2024, 1, 1),
        )
        assert cr.total_monthly == Decimal("71000000")


class TestBankAccountModel:
    def test_create(self, tenant, employee):
        ba = BankAccountModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            bank_name="بانک ملت",
            account_number="1234567890",
            sheba_number="IR000000000000000000000000",
            is_primary=True,
        )
        assert ba.pk is not None
        assert ba.is_primary is True


class TestCostAllocationModel:
    def test_create(self, tenant, employee):
        ca = CostAllocationModel.objects.create(
            id=uuid.uuid4(),
            tenant=tenant,
            employee=employee,
            allocation_type=CostAllocationModel.AllocationType.DEPARTMENT,
            target_id=uuid.uuid4(),
            percentage=Decimal("100.00"),
            is_active=True,
        )
        assert ca.pk is not None
        assert ca.percentage == Decimal("100.00")
