from __future__ import annotations

from datetime import date
from typing import TYPE_CHECKING

import structlog
from django.db import transaction

from simorgh.apps.hr_core.services.employee_no import _generate_employee_no
from simorgh.apps.hr_core.services.encryption import encrypt_field

_log = structlog.get_logger("simorgh.hr_core.services.employee")

if TYPE_CHECKING:
    from simorgh.apps.hr_core.models import Employee


def create_employee(
    *,
    tenant_id: int,
    organization_node_id: int,
    first_name: str,
    last_name: str,
    hire_date: date,
    employment_type: str = "full_time",
    user_id: int | None = None,
    display_name: str = "",
    national_id: str = "",
    birth_date: date | None = None,
    gender: str = "",
    mobile: str = "",
    personal_email: str = "",
    work_email: str = "",
    status: str = "active",
    department_id: int | None = None,
    manager_id: int | None = None,
    job_title_id: int | None = None,
    work_location: str = "",
) -> "Employee":
    from simorgh.apps.app_settings.services import resolve as resolve_setting
    from simorgh.apps.events.bus import dispatch_async
    from simorgh.apps.hr_core.models import Employee

    with transaction.atomic():
        max_employees = resolve_setting("hr.max_employees", tenant_id=tenant_id)
        if max_employees and max_employees > 0:
            current_count = (
                Employee.objects.select_for_update()
                .filter(
                    tenant_id=tenant_id, is_deleted=False,
                    status__in=["active", "on_leave"],
                )
                .count()
            )
            if current_count >= max_employees:
                _log.warning(
                    "hr_core.services.create_employee.quota_exceeded",
                    tenant_id=tenant_id,
                    current_count=current_count,
                    max_employees=max_employees,
                )
                raise ValueError(
                    f"Employee quota reached ({current_count}/{max_employees}). "
                    "Contact your administrator to increase the limit."
                )

        employee_no = _generate_employee_no(tenant_id)

        employee = Employee.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            user_id=user_id,
            employee_no=employee_no,
            first_name=first_name.strip(),
            last_name=last_name.strip(),
            display_name=display_name.strip() or f"{first_name.strip()} {last_name.strip()}",
            national_id=encrypt_field(national_id) if national_id else "",
            birth_date=birth_date,
            gender=gender,
            mobile=mobile,
            personal_email=personal_email,
            work_email=work_email,
            employment_type=employment_type,
            hire_date=hire_date,
            status=status,
            department_id=department_id,
            manager_id=manager_id,
            job_title_id=job_title_id,
            work_location=work_location,
        )

    dispatch_async(
        "hr.employee.hired",
        {
            "employee_id": employee.pk,
            "tenant_id": tenant_id,
            "department_id": department_id,
        },
        tenant_id=tenant_id,
    )

    from simorgh.core.audit import record_event as _audit
    _audit(
        "created",
        resource_type="hr.employee",
        resource_id=str(employee.public_id),
        after={"employee_no": employee.employee_no, "status": employee.status, "tenant_id": tenant_id},
        tenant_id=tenant_id,
    )
    _log.info(
        "hr_core.services.create_employee.done",
        employee_id=employee.pk,
        employee_no=employee.employee_no,
        tenant_id=tenant_id,
    )
    return employee


def update_employee(
    employee: "Employee",
    *,
    first_name: str | None = None,
    last_name: str | None = None,
    display_name: str | None = None,
    national_id: str | None = None,
    birth_date: date | None = None,
    gender: str | None = None,
    mobile: str | None = None,
    personal_email: str | None = None,
    work_email: str | None = None,
    employment_type: str | None = None,
    hire_date: date | None = None,
    status: str | None = None,
    department_id: int | None = None,
    manager_id: int | None = None,
    job_title_id: int | None = None,
    work_location: str | None = None,
) -> "Employee":
    from simorgh.apps.events.bus import dispatch

    old_dept_id = employee.department_id
    old_manager_id = employee.manager_id
    old_title_id = employee.job_title_id

    _sensitive_before: dict = {}
    _sensitive_fields = {"national_id", "work_email", "department_id", "employment_type"}
    for _f in _sensitive_fields:
        _sensitive_before[_f] = getattr(employee, _f, None)

    if first_name is not None:
        employee.first_name = first_name.strip()
    if last_name is not None:
        employee.last_name = last_name.strip()
    if display_name is not None:
        employee.display_name = display_name.strip()
    if national_id is not None:
        employee.national_id = encrypt_field(national_id) if national_id else ""
    if birth_date is not None:
        employee.birth_date = birth_date
    if gender is not None:
        employee.gender = gender
    if mobile is not None:
        employee.mobile = mobile
    if personal_email is not None:
        employee.personal_email = personal_email
    if work_email is not None:
        employee.work_email = work_email
    if employment_type is not None:
        employee.employment_type = employment_type
    if hire_date is not None:
        employee.hire_date = hire_date
    if status is not None:
        employee.status = status
    if department_id is not None:
        employee.department_id = department_id
    if manager_id is not None:
        employee.manager_id = manager_id
    if job_title_id is not None:
        employee.job_title_id = job_title_id
    if work_location is not None:
        employee.work_location = work_location

    employee.save()

    if department_id is not None and department_id != old_dept_id:
        dispatch(
            "hr.employee.department_changed",
            {
                "employee_id": employee.pk,
                "from_dept_id": old_dept_id,
                "to_dept_id": department_id,
                "tenant_id": employee.tenant_id,
            },
        )
    if manager_id is not None and manager_id != old_manager_id:
        dispatch(
            "hr.employee.manager_changed",
            {
                "employee_id": employee.pk,
                "from_manager_id": old_manager_id,
                "to_manager_id": manager_id,
                "tenant_id": employee.tenant_id,
            },
        )
    if job_title_id is not None and job_title_id != old_title_id:
        dispatch(
            "hr.employee.job_title_changed",
            {
                "employee_id": employee.pk,
                "from_title_id": old_title_id,
                "to_title_id": job_title_id,
                "tenant_id": employee.tenant_id,
            },
        )

    _sensitive_after = {_f: getattr(employee, _f, None) for _f in _sensitive_fields}
    if _sensitive_before != _sensitive_after:
        from simorgh.core.audit import record_event as _audit
        _audit(
            "updated",
            resource_type="hr.employee",
            resource_id=str(employee.public_id),
            before={k: v for k, v in _sensitive_before.items() if _sensitive_before[k] != _sensitive_after[k]},
            after={k: v for k, v in _sensitive_after.items() if _sensitive_before[k] != _sensitive_after[k]},
            tenant_id=employee.tenant_id,
        )

    return employee


def terminate_employee(
    employee: "Employee",
    *,
    termination_date: date,
    reason: str = "",
) -> "Employee":
    from simorgh.apps.events.bus import dispatch
    from simorgh.apps.hr_core.models.choices import EmployeeStatus

    if not termination_date:
        raise ValueError("termination_date is required.")
    if employee.status == EmployeeStatus.TERMINATED:
        raise ValueError("Employee is already terminated.")

    employee.status = EmployeeStatus.TERMINATED
    employee.termination_date = termination_date
    employee.save()

    dispatch(
        "hr.employee.terminated",
        {
            "employee_id": employee.pk,
            "tenant_id": employee.tenant_id,
            "termination_date": termination_date.isoformat(),
        },
    )

    from simorgh.core.audit import record_event as _audit
    _audit(
        "terminated",
        resource_type="hr.employee",
        resource_id=str(employee.public_id),
        after={"termination_date": termination_date.isoformat(), "reason": reason},
        tenant_id=employee.tenant_id,
    )
    _log.info(
        "hr_core.services.terminate_employee.done",
        employee_id=employee.pk,
        tenant_id=employee.tenant_id,
        termination_date=termination_date.isoformat(),
    )
    return employee


def resign_employee(
    employee: "Employee",
    *,
    resignation_date: date,
    reason: str = "",
) -> "Employee":
    from simorgh.apps.events.bus import dispatch
    from simorgh.apps.hr_core.models.choices import EmployeeStatus

    if not resignation_date:
        raise ValueError("resignation_date is required.")
    if employee.status in (EmployeeStatus.TERMINATED, EmployeeStatus.RESIGNED):
        raise ValueError(f"Employee is already {employee.status}.")

    employee.status = EmployeeStatus.RESIGNED
    employee.termination_date = resignation_date
    employee.save()

    dispatch(
        "hr.employee.resigned",
        {
            "employee_id": employee.pk,
            "tenant_id": employee.tenant_id,
            "resignation_date": resignation_date.isoformat(),
        },
    )

    from simorgh.core.audit import record_event as _audit
    _audit(
        "resigned",
        resource_type="hr.employee",
        resource_id=str(employee.public_id),
        after={"resignation_date": resignation_date.isoformat(), "reason": reason},
        tenant_id=employee.tenant_id,
    )
    _log.info(
        "hr_core.services.resign_employee.done",
        employee_id=employee.pk,
        tenant_id=employee.tenant_id,
        resignation_date=resignation_date.isoformat(),
    )
    return employee
