from __future__ import annotations

from datetime import date as _date, datetime
from typing import TYPE_CHECKING

from django.db import models, transaction

if TYPE_CHECKING:
    from simorgh.apps.hr_core.models import (
        AttendanceRecord,
        Employee,
        EmployeeShift,
        ShiftDefinition,
    )


def _compute_work_minutes(check_in, check_out, break_minutes: int = 0) -> int | None:
    if check_in is None or check_out is None:
        return None
    base = _date(2000, 1, 1)
    ci = datetime.combine(base, check_in)
    co = datetime.combine(base, check_out)
    diff = int((co - ci).total_seconds() // 60)
    return max(0, diff - break_minutes)


# ── Shift ────────────────────────────────────────────────────────────────

def create_shift(tenant, name: str, start_time, end_time, work_days: list, break_minutes: int = 0):
    from simorgh.apps.hr_core.models import ShiftDefinition
    return ShiftDefinition.objects.create(
        tenant=tenant, name=name, start_time=start_time, end_time=end_time,
        work_days=work_days, break_minutes=break_minutes,
    )


def update_shift(shift, **fields):
    allowed = {"name", "start_time", "end_time", "work_days", "break_minutes"}
    for key, value in fields.items():
        if key in allowed:
            setattr(shift, key, value)
    shift.save()
    return shift


def delete_shift(shift) -> None:
    from simorgh.apps.hr_core.models import EmployeeShift
    if EmployeeShift.objects.filter(shift=shift).exists():
        raise ValueError("Cannot delete a shift that has active employee assignments.")
    shift.delete()


def assign_shift_to_employee(employee, shift, effective_from, effective_to=None):
    from simorgh.apps.hr_core.models import EmployeeShift
    return EmployeeShift.objects.create(
        tenant_id=employee.tenant_id,
        organization_node_id=employee.organization_node_id,
        employee=employee, shift=shift,
        effective_from=effective_from, effective_to=effective_to,
    )


# ── Attendance ───────────────────────────────────────────────────────────

@transaction.atomic
def check_in_employee(tenant, employee, date, time, source: str = "manual") -> "AttendanceRecord":
    from simorgh.apps.events.bus import dispatch
    from simorgh.apps.hr_core.models import AttendanceRecord

    try:
        record = AttendanceRecord.objects.select_for_update().get(
            tenant=tenant, employee=employee, date=date,
        )
        if record.is_finalized:
            raise ValueError("Attendance record is finalized and cannot be edited.")
        record.check_in = time
        record.source = source
        record.save()
    except AttendanceRecord.DoesNotExist:
        record = AttendanceRecord.objects.create(
            tenant=tenant,
            employee=employee,
            date=date,
            organization_node_id=employee.organization_node_id,
            check_in=time,
            source=source,
        )

    dispatch(
        "hr.attendance.checked_in",
        {"employee_id": employee.pk, "date": str(date), "time": str(time), "tenant_id": tenant.pk},
    )
    return record


@transaction.atomic
def check_out_employee(tenant, employee, date, time) -> "AttendanceRecord":
    from simorgh.apps.hr_core.models import AttendanceRecord, EmployeeShift

    try:
        record = AttendanceRecord.objects.select_for_update().get(
            tenant=tenant, employee=employee, date=date,
        )
    except AttendanceRecord.DoesNotExist:
        raise ValueError("No check-in record found for this date.")
    if record.is_finalized:
        raise ValueError("Attendance record is finalized and cannot be edited.")

    record.check_out = time
    break_minutes = 0
    assignment = (
        EmployeeShift.objects.filter(employee=employee, effective_from__lte=date)
        .filter(models.Q(effective_to__isnull=True) | models.Q(effective_to__gte=date))
        .select_related("shift").first()
    )
    if assignment:
        break_minutes = assignment.shift.break_minutes
    record.work_minutes = _compute_work_minutes(record.check_in, time, break_minutes)
    record.save()
    return record


def create_attendance_record(tenant, employee, date, check_in=None, check_out=None,
                             source: str = "manual", note: str = "", break_minutes: int = 0) -> "AttendanceRecord":
    from simorgh.apps.hr_core.models import AttendanceRecord
    work_minutes = _compute_work_minutes(check_in, check_out, break_minutes)
    record, _ = AttendanceRecord.objects.update_or_create(
        tenant=tenant, employee=employee, date=date,
        defaults={
            "organization_node_id": employee.organization_node_id,
            "check_in": check_in, "check_out": check_out,
            "work_minutes": work_minutes, "source": source, "note": note,
        },
    )
    return record


def update_attendance_record(record, **fields) -> "AttendanceRecord":
    if record.is_finalized:
        raise ValueError("Attendance record is finalized and cannot be edited.")
    allowed = {"check_in", "check_out", "source", "note", "work_minutes"}
    for key, value in fields.items():
        if key in allowed:
            setattr(record, key, value)
    if "check_in" in fields or "check_out" in fields:
        record.work_minutes = _compute_work_minutes(record.check_in, record.check_out)
    record.save()
    return record


def finalize_attendance_record(record) -> "AttendanceRecord":
    from simorgh.apps.events.bus import dispatch
    from simorgh.apps.hr_core.models import EmployeeShift
    from simorgh.apps.app_settings.services import resolve as resolve_setting

    if record.is_finalized:
        raise ValueError("Attendance record is already finalized.")

    overtime_threshold = resolve_setting(
        "hr.attendance.overtime_threshold_minutes", tenant_id=record.tenant_id
    ) or 30

    expected_minutes = 0
    assignment = (
        EmployeeShift.objects.filter(employee=record.employee, effective_from__lte=record.date)
        .filter(models.Q(effective_to__isnull=True) | models.Q(effective_to__gte=record.date))
        .select_related("shift").first()
    )
    if assignment:
        expected_minutes = assignment.shift.expected_work_minutes

    if record.work_minutes is not None and expected_minutes > 0:
        excess = record.work_minutes - expected_minutes
        if excess >= overtime_threshold:
            record.overtime_minutes = excess

    record.is_finalized = True
    record.save()

    dispatch(
        "hr.attendance.finalized",
        {"employee_id": record.employee_id, "date": str(record.date),
         "work_minutes": record.work_minutes, "tenant_id": record.tenant_id},
    )
    return record


def delete_attendance_record(record) -> None:
    if record.is_finalized:
        raise ValueError("Finalized attendance records cannot be deleted.")
    record.delete()
