import calendar
from datetime import date, datetime, time, timedelta
from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from django.utils.dateparse import parse_datetime

from apps.audit.services import log_event
from apps.organization.models import Holiday
from apps.organization.services import get_effective_policy
from .models import (
    AttendanceAdjustment,
    AttendanceDayLedger,
    AttendancePeriod,
    AttendanceRecord,
    DeviceEmployeeMap,
    OvertimeEntry,
    PunchLog,
)


def _aware(dt):
    if dt is None:
        return None
    if timezone.is_naive(dt):
        return timezone.make_aware(dt, timezone.get_current_timezone())
    return dt


def recalculate_attendance(record):
    """Recalculate neutral time metrics. Payable OT still requires approval."""
    record.worked_minutes = 0
    record.late_minutes = 0
    record.early_exit_minutes = 0
    record.extra_minutes = 0
    shift = getattr(record.employee, "shift", None)
    if record.check_in and record.check_out:
        gross_minutes = max(0, int((record.check_out - record.check_in).total_seconds() // 60))
        break_minutes = int(getattr(shift, "break_minutes", 0) or 0) if shift else 0
        record.worked_minutes = max(0, gross_minutes - break_minutes)
    if shift and record.check_in:
        start = _aware(datetime.combine(record.work_date, shift.start_time))
        late_after = start + timedelta(minutes=shift.grace_minutes)
        record.late_minutes = max(0, int((record.check_in - late_after).total_seconds() // 60))
    if shift and record.check_out:
        end_date = record.work_date
        if shift.end_time <= shift.start_time:
            end_date += timedelta(days=1)
        end = _aware(datetime.combine(end_date, shift.end_time))
        delta = int((record.check_out - end).total_seconds() // 60)
        record.extra_minutes = max(0, delta)
        record.early_exit_minutes = max(0, -delta)
    if not record.employee.ot_eligible:
        record.payable_ot_minutes = 0
    return record


def validate_attendance_values(employee, work_date, check_in, check_out, status):
    """Fail closed on inconsistent day/timestamp/status combinations."""
    check_in = _aware(check_in)
    check_out = _aware(check_out)
    if check_out and not check_in:
        raise ValidationError("Check-in is required when check-out is entered.")
    if check_in and check_out and check_out <= check_in:
        raise ValidationError("Check-out must be later than check-in.")
    if check_in and timezone.localtime(check_in).date() != work_date:
        raise ValidationError("Check-in date must match the attendance work date.")
    if check_out:
        out_date = timezone.localtime(check_out).date()
        if out_date not in {work_date, work_date + timedelta(days=1)}:
            raise ValidationError("Check-out date must be the work date or the following day.")
    no_punch_statuses = {
        AttendanceRecord.Status.ABSENT, AttendanceRecord.Status.LEAVE,
        AttendanceRecord.Status.HOLIDAY, AttendanceRecord.Status.WEEKLY_OFF,
    }
    if status in no_punch_statuses and (check_in or check_out):
        raise ValidationError("Absent/Leave/Holiday/Weekly Off attendance cannot contain punch timestamps.")
    if status in {AttendanceRecord.Status.PRESENT, AttendanceRecord.Status.LATE} and not check_in:
        raise ValidationError("Present/Late attendance requires a check-in timestamp.")
    if status == AttendanceRecord.Status.LEAVE and not approved_leave_for_day(employee, work_date):
        raise ValidationError("Leave attendance is controlled by the approved leave workflow; approve the leave request first.")
    if status in {AttendanceRecord.Status.HOLIDAY, AttendanceRecord.Status.WEEKLY_OFF}:
        expected_calendar_status = _holiday_status(employee, work_date)
        if expected_calendar_status != status:
            raise ValidationError("Holiday/Weekly Off attendance must match the configured company calendar and employee weekly holiday.")
    return check_in, check_out


def employee_final_working_day(employee):
    # Once exit is completed, Employee.employment_end_date is the canonical service end.
    if getattr(employee, "employment_end_date", None):
        return employee.employment_end_date
    # During an in-progress exit, use the finalized ExitCase date.
    from apps.exits.models import ExitCase
    return (
        ExitCase.objects.filter(employee=employee, final_working_day__isnull=False)
        .order_by("-final_working_day", "-pk")
        .values_list("final_working_day", flat=True)
        .first()
    )


def employee_in_service_on(employee, work_date):
    if work_date < employee.joining_date:
        return False
    final_day = employee_final_working_day(employee)
    if final_day and work_date > final_day:
        return False
    return True


def _holiday_status(employee, work_date):
    weekly = (employee.weekly_holiday or "").strip().lower()
    if weekly and work_date.strftime("%A").lower() == weekly:
        return AttendanceRecord.Status.WEEKLY_OFF
    is_holiday = Holiday.objects.filter(
        company=employee.company,
        holiday_date=work_date,
        is_active=True,
    ).filter(Q(branch__isnull=True) | Q(branch=employee.branch)).exists()
    if is_holiday:
        return AttendanceRecord.Status.HOLIDAY
    return None


def approved_leave_for_day(employee, work_date):
    from apps.leave.models import LeaveRequest
    return (
        LeaveRequest.objects.select_related("leave_type")
        .filter(employee=employee, status="APPROVED", start_date__lte=work_date, end_date__gte=work_date)
        .order_by("pk")
        .first()
    )


def _punch_window(employee, work_date):
    shift = getattr(employee, "shift", None)
    if shift:
        start = datetime.combine(work_date, time.min)
        next_day = work_date + timedelta(days=1)
        # Overnight shifts may end the next morning. Include a conservative window.
        end = datetime.combine(next_day, time(12, 0)) if shift.end_time <= shift.start_time else datetime.combine(next_day, time.min)
    else:
        start = datetime.combine(work_date, time.min)
        end = datetime.combine(work_date + timedelta(days=1), time.min)
    return _aware(start), _aware(end)


def punches_for_day(employee, work_date):
    start, end = _punch_window(employee, work_date)
    return PunchLog.objects.filter(employee=employee, punch_time__gte=start, punch_time__lt=end).order_by("punch_time", "pk")


@transaction.atomic
def sync_attendance_from_punches(employee, work_date, *, user=None):
    """Create/update a daily AttendanceRecord from mapped device punches.

    First punch is Check-In and last distinct punch is Check-Out. A single punch
    is retained as Check-In and remains available for HR exception review.
    """
    if not employee_in_service_on(employee, work_date):
        return None
    punches = list(punches_for_day(employee, work_date))
    if not punches:
        return None
    record, _ = AttendanceRecord.objects.select_for_update().get_or_create(
        employee=employee,
        work_date=work_date,
        defaults={"status": AttendanceRecord.Status.PRESENT, "remarks": "Created from attendance device punches"},
    )
    if record.finalized:
        return record
    first = punches[0].punch_time
    last = punches[-1].punch_time if len(punches) > 1 else None
    record.check_in = first
    record.check_out = last
    record.status = AttendanceRecord.Status.PRESENT
    recalculate_attendance(record)
    if record.late_minutes > 0:
        record.status = AttendanceRecord.Status.LATE
    record.remarks = "Synced from attendance device punches"
    record.save()
    reconcile_attendance_day(employee, work_date, force=True)
    log_event(
        user=user,
        action="PUNCH_TO_ATTENDANCE",
        module="attendance",
        object_id=record.pk,
        object_repr=f"{employee} / {work_date}",
        new_values={"punches": len(punches), "status": record.status, "check_in": str(record.check_in), "check_out": str(record.check_out or "")},
    )
    return record


@transaction.atomic
def reconcile_attendance_day(employee, work_date, *, force=False):
    """Build one authoritative day ledger for attendance, leave and payroll.

    Priority: service dates -> weekly off/holiday -> approved leave -> recorded
    attendance/device punch -> missing workday absence. Paid leave never becomes an
    absence, so payroll cannot double-deduct the same day.
    """
    if not employee_in_service_on(employee, work_date):
        AttendanceDayLedger.objects.filter(employee=employee, work_date=work_date, finalized=False).delete()
        return None
    period = AttendancePeriod.objects.select_for_update().filter(
        company=employee.company, year=work_date.year, month=work_date.month
    ).first()
    if period and period.status == AttendancePeriod.Status.FINALIZED:
        raise ValidationError(
            "Attendance period is finalized. Reopen the attendance period before any reconciliation/correction."
        )
    policy = get_effective_policy(employee.company, work_date)
    existing = AttendanceRecord.objects.select_for_update().filter(employee=employee, work_date=work_date).first()
    if existing and existing.finalized and not force:
        return AttendanceDayLedger.objects.filter(employee=employee, work_date=work_date).first()

    holiday_status = _holiday_status(employee, work_date)
    leave = None
    source = "ATTENDANCE"
    deduction_fraction = Decimal("0.00")
    payable_fraction = Decimal("1.00")

    if holiday_status:
        status = holiday_status
        source = "CALENDAR"
        record, _ = AttendanceRecord.objects.get_or_create(employee=employee, work_date=work_date, defaults={"status": status})
        if not record.finalized:
            record.status = status
            record.check_in = None
            record.check_out = None
            recalculate_attendance(record)
            record.save()
    else:
        leave = approved_leave_for_day(employee, work_date)
        if leave:
            is_half_day_leave = leave.start_date == leave.end_date and Decimal(leave.days) == Decimal("0.50")
            status = AttendanceRecord.Status.HALF_DAY if is_half_day_leave else AttendanceRecord.Status.LEAVE
            source = "LEAVE"
            if leave.leave_type.is_paid:
                deduction_fraction = Decimal("0.00")
                payable_fraction = Decimal("1.00")
            else:
                deduction_fraction = Decimal(policy.half_day_deduction_fraction) if is_half_day_leave else Decimal("1.00")
                payable_fraction = Decimal("1.00") - deduction_fraction
            record, _ = AttendanceRecord.objects.get_or_create(employee=employee, work_date=work_date, defaults={"status": status})
            if not record.finalized:
                record.status = status
                # Full-day leave is authoritative for the daily attendance roll-up.
                # Raw device punches remain preserved in PunchLog for audit/review.
                if not is_half_day_leave:
                    record.check_in = None
                    record.check_out = None
                    recalculate_attendance(record)
                    record.remarks = f"Approved leave: {leave.leave_type.name}"
                    record.save()
                else:
                    record.remarks = f"Approved leave: {leave.leave_type.name}"
                    record.save(update_fields=["status", "remarks", "updated_at"])
        else:
            record = existing
            if record is None:
                # Device punches may already exist even if the roll-up has not run.
                record = sync_attendance_from_punches(employee, work_date)
            if record is None:
                status = AttendanceRecord.Status.ABSENT if policy.missing_workday_is_absent else AttendanceRecord.Status.PRESENT
                source = "MISSING_DAY"
                record = AttendanceRecord.objects.create(employee=employee, work_date=work_date, status=status, remarks="Generated by attendance reconciliation")
            else:
                status = record.status
                incomplete_punch = bool(record.check_in) != bool(record.check_out)
                if incomplete_punch:
                    source = "MISSING_PUNCH"
                    if policy.missing_punch_action == "ABSENT":
                        status = AttendanceRecord.Status.ABSENT
                        record.status = status
                        record.remarks = "Incomplete punch treated as absent by company policy"
                        if not record.finalized:
                            record.save(update_fields=["status", "remarks", "updated_at"])
            if status == AttendanceRecord.Status.ABSENT:
                deduction_fraction = Decimal("1.00")
                payable_fraction = Decimal("0.00")
            elif status == AttendanceRecord.Status.HALF_DAY:
                deduction_fraction = Decimal(policy.half_day_deduction_fraction)
                payable_fraction = Decimal("1.00") - deduction_fraction
            else:
                deduction_fraction = Decimal("0.00")
                payable_fraction = Decimal("1.00")

    ledger, _ = AttendanceDayLedger.objects.update_or_create(
        employee=employee,
        work_date=work_date,
        defaults={
            "company": employee.company,
            "attendance_record": record,
            "leave_request": leave,
            "day_status": status,
            "source": source,
            "deduction_fraction": deduction_fraction,
            "payable_fraction": payable_fraction,
            "finalized": bool(record.finalized),
            "policy_snapshot": {
                "policy_id": policy.pk,
                "salary_day_divisor": str(policy.salary_day_divisor),
                "proration_method": policy.salary_proration_method,
                "half_day_deduction_fraction": str(policy.half_day_deduction_fraction),
                "missing_punch_action": policy.missing_punch_action,
            },
        },
    )
    return ledger



@transaction.atomic
def restore_attendance_after_leave_change(employee, work_date):
    """Remove an approved-leave overlay and rebuild attendance from raw punches/policy."""
    period = AttendancePeriod.objects.select_for_update().filter(
        company=employee.company, year=work_date.year, month=work_date.month
    ).first()
    if period and period.status == AttendancePeriod.Status.FINALIZED:
        raise ValidationError("Attendance period is finalized. Reopen it before cancelling/reversing approved leave.")

    holiday_status = _holiday_status(employee, work_date)
    record = AttendanceRecord.objects.select_for_update().filter(employee=employee, work_date=work_date).first()
    if record and record.finalized:
        raise ValidationError("Attendance record is finalized. Reopen the attendance period before leave reversal.")

    if record and not holiday_status and record.status in {AttendanceRecord.Status.LEAVE, AttendanceRecord.Status.HALF_DAY}:
        punches = list(punches_for_day(employee, work_date))
        if punches:
            record.check_in = punches[0].punch_time
            record.check_out = punches[-1].punch_time if len(punches) > 1 else None
            record.status = AttendanceRecord.Status.PRESENT
            recalculate_attendance(record)
            if record.late_minutes > 0:
                record.status = AttendanceRecord.Status.LATE
            record.remarks = "Rebuilt from device punches after leave cancellation"
        else:
            policy = get_effective_policy(employee.company, work_date)
            record.check_in = None
            record.check_out = None
            record.status = AttendanceRecord.Status.ABSENT if policy.missing_workday_is_absent else AttendanceRecord.Status.PRESENT
            record.remarks = "Reconciled after leave cancellation"
            recalculate_attendance(record)
        record.save()
    return reconcile_attendance_day(employee, work_date, force=True)

def _month_bounds(year, month):
    return date(year, month, 1), date(year, month, calendar.monthrange(year, month)[1])


def attendance_period_exceptions(company, year, month):
    """Return unresolved attendance/data-integrity exceptions before month close."""
    start, end = _month_bounds(year, month)
    policy = get_effective_policy(company, end)
    from apps.employees.models import Employee
    exceptions = []
    pending_ot = OvertimeEntry.objects.filter(
        employee__company=company, work_date__range=(start, end), status="PENDING"
    ).select_related("employee").order_by("work_date", "employee__employee_code")
    for item in pending_ot:
        exceptions.append({
            "employee": item.employee, "date": item.work_date, "type": "PENDING_OT",
            "detail": "Pending overtime approval must be resolved before attendance month finalization",
        })

    employees = Employee.objects.filter(company=company, joining_date__lte=end).select_related("branch", "shift").order_by("employee_code")
    for employee in employees:
        final_day = employee_final_working_day(employee)
        employee_end = min(end, final_day) if final_day else end
        current = max(start, employee.joining_date)
        while current <= employee_end:
            holiday_status = _holiday_status(employee, current)
            leave = approved_leave_for_day(employee, current)
            record = AttendanceRecord.objects.filter(employee=employee, work_date=current).first()

            if record and record.status == AttendanceRecord.Status.LEAVE and not leave:
                exceptions.append({
                    "employee": employee, "date": current, "type": "LEAVE_WITHOUT_APPROVAL",
                    "detail": "Attendance is marked Leave but no approved leave request covers the date",
                })
            if record and record.status in {AttendanceRecord.Status.HOLIDAY, AttendanceRecord.Status.WEEKLY_OFF} and record.status != holiday_status:
                exceptions.append({
                    "employee": employee, "date": current, "type": "CALENDAR_STATUS_MISMATCH",
                    "detail": "Holiday/Weekly Off status does not match the configured calendar",
                })

            if policy.missing_punch_action == "BLOCK" and not holiday_status and not leave:
                if record is None:
                    punches = list(punches_for_day(employee, current)[:2])
                    if len(punches) == 1:
                        exceptions.append({"employee": employee, "date": current, "type": "MISSING_PUNCH", "detail": "Only one device punch found"})
                elif bool(record.check_in) != bool(record.check_out):
                    exceptions.append({"employee": employee, "date": current, "type": "MISSING_PUNCH", "detail": "Check-in/check-out pair is incomplete"})
            current += timedelta(days=1)
    return exceptions


@transaction.atomic
def finalize_attendance_period(company, year, month, *, user=None):
    period, _ = AttendancePeriod.objects.select_for_update().get_or_create(company=company, year=year, month=month)
    if period.status == AttendancePeriod.Status.FINALIZED:
        return period
    start, end = _month_bounds(year, month)
    get_effective_policy(company, end)  # hard gate: payroll policy must exist
    exceptions = attendance_period_exceptions(company, year, month)
    if exceptions:
        sample = ", ".join(f"{x['employee'].employee_code} {x['date']}" for x in exceptions[:5])
        raise ValidationError(f"Attendance finalization blocked by {len(exceptions)} unresolved exception(s): {sample}. Resolve/correct them or change the effective missing-punch policy.")
    from apps.employees.models import Employee
    employees = Employee.objects.filter(company=company, joining_date__lte=end).order_by("employee_code")
    total_ledgers = 0
    for employee in employees:
        final_day = employee_final_working_day(employee)
        employee_end = min(end, final_day) if final_day else end
        current = max(start, employee.joining_date)
        while current <= employee_end:
            ledger = reconcile_attendance_day(employee, current, force=True)
            if ledger:
                AttendanceRecord.objects.filter(pk=ledger.attendance_record_id).update(finalized=True)
                AttendanceDayLedger.objects.filter(pk=ledger.pk).update(finalized=True)
                total_ledgers += 1
            current += timedelta(days=1)
    period.status = AttendancePeriod.Status.FINALIZED
    period.finalized_by = user
    period.finalized_at = timezone.now()
    period.reopened_by = None
    period.reopened_at = None
    period.reopen_reason = ""
    period.save()
    log_event(user=user, action="FINALIZE", module="attendance_period", object_id=period.pk, object_repr=f"{company.code}/{year}-{month:02d}", new_values={"day_ledgers": total_ledgers})
    return period


@transaction.atomic
def reopen_attendance_period(period, *, user=None, reason=""):
    if not (reason or "").strip():
        raise ValidationError("A reopen reason is required.")
    period = AttendancePeriod.objects.select_for_update().get(pk=period.pk)
    if period.status != AttendancePeriod.Status.FINALIZED:
        raise ValidationError("Only a finalized attendance period can be reopened.")

    # A reviewed/approved/locked payroll is already a controlled financial state.
    # Reopening attendance underneath it would invalidate the payroll evidence.
    from apps.payroll.models import PayrollPeriod
    payroll = PayrollPeriod.objects.filter(
        company=period.company, year=period.year, month=period.month
    ).first()
    if payroll and payroll.status != PayrollPeriod.Status.DRAFT:
        raise ValidationError(
            "Attendance cannot be reopened while payroll is Reviewed/Approved/Locked. "
            "Roll payroll back through the controlled payroll workflow first."
        )
    start, end = _month_bounds(period.year, period.month)
    AttendanceRecord.objects.filter(employee__company=period.company, work_date__range=(start, end)).update(finalized=False)
    AttendanceDayLedger.objects.filter(company=period.company, work_date__range=(start, end)).update(finalized=False)
    period.status = AttendancePeriod.Status.OPEN
    period.reopened_by = user
    period.reopened_at = timezone.now()
    period.reopen_reason = reason.strip()
    period.save()
    log_event(user=user, action="REOPEN", module="attendance_period", object_id=period.pk, object_repr=f"{period.company.code}/{period.year}-{period.month:02d}", reason=reason.strip())
    return period


def assert_attendance_period_finalized(company, year, month):
    period = AttendancePeriod.objects.filter(
        company=company, year=year, month=month, status=AttendancePeriod.Status.FINALIZED
    ).first()
    if not period or not period.finalized_at:
        raise ValidationError("Attendance period must be finalized before payroll calculation/review.")
    return period


def attendance_period_fingerprint(company, year, month):
    """Stable identifier for the currently-finalized attendance close.

    Reopening and re-finalizing changes ``finalized_at``. Payroll snapshots built
    against an older close therefore cannot be reviewed accidentally.
    """
    period = assert_attendance_period_finalized(company, year, month)
    return f"{period.pk}:{period.finalized_at.isoformat()}"


@transaction.atomic
def save_attendance_record(form, *, user=None, request=None):
    record = form.save(commit=False)
    old_values = {}
    action = "CREATE"
    if record.pk:
        current = AttendanceRecord.objects.select_for_update().get(pk=record.pk)
        if current.finalized:
            raise ValidationError("Finalized attendance cannot be edited. Reopen the attendance period first.")
        old_values = {
            "employee": current.employee_id,
            "work_date": str(current.work_date),
            "check_in": current.check_in.isoformat() if current.check_in else "",
            "check_out": current.check_out.isoformat() if current.check_out else "",
            "status": current.status,
        }
        action = "UPDATE"
    record.check_in, record.check_out = validate_attendance_values(
        record.employee, record.work_date, record.check_in, record.check_out, record.status
    )
    recalculate_attendance(record)
    record.save()
    reconcile_attendance_day(record.employee, record.work_date, force=True)
    log_event(
        user=user, action=action, module="attendance", object_id=record.pk,
        object_repr=f"{record.employee} / {record.work_date}", old_values=old_values,
        new_values={"employee": record.employee_id, "work_date": str(record.work_date), "status": record.status, "worked_minutes": record.worked_minutes, "late_minutes": record.late_minutes, "extra_minutes": record.extra_minutes},
        request=request,
    )
    return record


@transaction.atomic
def create_missing_attendance_adjustment_request(*, employee, work_date, cleaned_data, user=None, request=None):
    """Create a correction request when no AttendanceRecord exists yet.

    The placeholder record is created through the authoritative reconciliation
    service, so missing-day policy and the day ledger stay consistent. The
    employee's requested values are applied only after workflow approval.
    """
    if not employee_in_service_on(employee, work_date):
        raise ValidationError("Attendance correction date must fall within the employee service period.")
    period = AttendancePeriod.objects.select_for_update().filter(
        company=employee.company, year=work_date.year, month=work_date.month
    ).first()
    if period and period.status == AttendancePeriod.Status.FINALIZED:
        raise ValidationError("Attendance period is finalized. HR must reopen it before a correction request can be created.")
    attendance = AttendanceRecord.objects.select_for_update().filter(employee=employee, work_date=work_date).first()
    if attendance is None:
        attendance = reconcile_attendance_day(employee, work_date, force=True).attendance_record
    return create_adjustment_request(
        attendance=attendance, cleaned_data=cleaned_data, user=user, request=request
    )


@transaction.atomic
def create_adjustment_request(*, attendance, cleaned_data, user=None, request=None):
    attendance = AttendanceRecord.objects.select_for_update().select_related("employee__reporting_manager__user").get(pk=attendance.pk)
    if attendance.finalized:
        raise ValidationError("Finalized attendance cannot be corrected until the attendance period is formally reopened.")
    new_check_in = cleaned_data.get("new_check_in")
    new_check_out = cleaned_data.get("new_check_out")
    new_check_in, new_check_out = validate_attendance_values(
        attendance.employee, attendance.work_date, new_check_in, new_check_out, cleaned_data.get("new_status")
    )
    requester_user = user if getattr(user, "is_authenticated", False) else None
    requester_employee = getattr(requester_user, "employee_profile", None) if requester_user else None
    is_self_service = bool(requester_employee and requester_employee.pk == attendance.employee_id)
    adjustment = AttendanceAdjustment.objects.create(
        attendance=attendance,
        requested_by_user=requester_user,
        requester_employee=requester_employee,
        requested_by_employee=is_self_service,
        reason=cleaned_data["reason"].strip(),
        old_values={"check_in": attendance.check_in.isoformat() if attendance.check_in else None, "check_out": attendance.check_out.isoformat() if attendance.check_out else None, "status": attendance.status},
        new_values={"check_in": new_check_in.isoformat() if new_check_in else None, "check_out": new_check_out.isoformat() if new_check_out else None, "status": cleaned_data["new_status"]},
        status="PENDING",
    )
    from apps.workflow.services import create_workflow_task_for_employee
    create_workflow_task_for_employee(employee=attendance.employee, module="Attendance", reference_no=f"ATT-ADJ-{adjustment.pk:06d}", title=f"Attendance correction: {attendance.employee.full_name} / {attendance.work_date}", source_type="attendance_adjustment", source_id=adjustment.pk, priority="MEDIUM", preferred_role="Manager")
    log_event(user=user, action="REQUEST_CORRECTION", module="attendance", object_id=adjustment.pk, object_repr=f"{attendance.employee} / {attendance.work_date}", new_values=adjustment.new_values, reason=adjustment.reason, request=request)
    return adjustment



@transaction.atomic
def resubmit_attendance_adjustment(adjustment, *, cleaned_data, user=None, request=None):
    adjustment = AttendanceAdjustment.objects.select_for_update().select_related(
        "attendance__employee", "requested_by_user", "requester_employee"
    ).get(pk=adjustment.pk)
    if adjustment.status != "RETURNED":
        raise ValidationError("Only a returned attendance correction can be resubmitted.")
    employee = adjustment.attendance.employee
    if adjustment.requested_by_user_id:
        if adjustment.requested_by_user_id != getattr(user, "id", None):
            raise ValidationError("Only the original requester may resubmit this attendance correction.")
    elif adjustment.requested_by_employee and getattr(employee, "user_id", None) != getattr(user, "id", None):
        # Legacy fallback for records created before requester identity was stored.
        raise ValidationError("Only the original requester may resubmit this attendance correction.")
    if adjustment.attendance.finalized:
        raise ValidationError("Attendance period is finalized. HR must reopen the period before a correction can be resubmitted.")
    new_check_in = cleaned_data.get("new_check_in")
    new_check_out = cleaned_data.get("new_check_out")
    new_check_in, new_check_out = validate_attendance_values(
        employee, adjustment.attendance.work_date, new_check_in, new_check_out, cleaned_data.get("new_status")
    )
    adjustment.new_values = {
        "check_in": new_check_in.isoformat() if new_check_in else None,
        "check_out": new_check_out.isoformat() if new_check_out else None,
        "status": cleaned_data.get("new_status"),
    }
    adjustment.reason = cleaned_data.get("reason", "").strip()
    adjustment.status = "PENDING"
    adjustment.save(update_fields=["new_values", "reason", "status", "updated_at"])
    from apps.workflow.services import create_workflow_task_for_employee
    create_workflow_task_for_employee(
        employee=employee, module="Attendance", reference_no=f"ATT-ADJ-{adjustment.pk:06d}",
        title=f"Resubmitted attendance correction: {employee.full_name} / {adjustment.attendance.work_date}",
        source_type="attendance_adjustment", source_id=adjustment.pk, priority="MEDIUM", preferred_role="Manager",
        payload={"resubmitted": True},
    )
    log_event(user=user, action="RESUBMIT_CORRECTION", module="attendance", object_id=adjustment.pk, object_repr=f"{employee} / {adjustment.attendance.work_date}", new_values=adjustment.new_values, reason=adjustment.reason, request=request)
    return adjustment

def _coerce_datetime(value):
    if not value:
        return None
    if isinstance(value, datetime):
        return value
    parsed = parse_datetime(str(value))
    return _aware(parsed) if parsed else None


@transaction.atomic
def apply_attendance_adjustment(adjustment, *, decision, note="", user=None):
    adjustment = AttendanceAdjustment.objects.select_for_update().select_related("attendance__employee__shift").get(pk=adjustment.pk)
    decision = decision.upper().strip()
    if decision not in {"APPROVED", "REJECTED", "RETURNED"}:
        raise ValidationError("Unsupported attendance adjustment decision.")
    if adjustment.status not in {"PENDING", "RETURNED"}:
        raise ValidationError("This attendance adjustment has already been finalized.")
    if decision in {"REJECTED", "RETURNED"} and not note.strip():
        raise ValidationError("Reason/comment is required.")
    attendance = adjustment.attendance
    if attendance.finalized:
        raise ValidationError("Attendance period is finalized. Reopen it before applying a correction.")
    if decision == "APPROVED":
        values = adjustment.new_values or {}
        attendance.check_in = _coerce_datetime(values.get("check_in"))
        attendance.check_out = _coerce_datetime(values.get("check_out"))
        if values.get("status"):
            attendance.status = values["status"]
        attendance.check_in, attendance.check_out = validate_attendance_values(
            attendance.employee, attendance.work_date, attendance.check_in, attendance.check_out, attendance.status
        )
        recalculate_attendance(attendance)
        attendance.save()
        reconcile_attendance_day(attendance.employee, attendance.work_date, force=True)
    adjustment.status = decision
    if note.strip():
        adjustment.reason = f"{adjustment.reason}\nReviewer: {note.strip()}".strip()
    adjustment.save(update_fields=["status", "reason", "updated_at"])
    return adjustment


@transaction.atomic
def ingest_punch(*, device, device_user_id, punch_time, punch_type="", raw_payload=None, user=None):
    mapping = DeviceEmployeeMap.objects.filter(device=device, device_user_id=str(device_user_id), is_active=True).select_related("employee").first()
    if mapping and mapping.employee.company_id != device.company_id:
        raise ValidationError("Mapped employee and attendance device must belong to the same company.")
    punch, created = PunchLog.objects.get_or_create(
        device=device, device_user_id=str(device_user_id), punch_time=punch_time,
        defaults={"employee": mapping.employee if mapping else None, "punch_type": punch_type, "raw_payload": raw_payload or {}},
    )
    if created and mapping:
        punch.employee = mapping.employee
        punch.save(update_fields=["employee", "updated_at"])
        sync_attendance_from_punches(mapping.employee, timezone.localtime(punch_time).date(), user=user)
    if created:
        log_event(user=user, action="INGEST_PUNCH", module="attendance_device", object_id=punch.pk, object_repr=f"{device.name} / {device_user_id} / {punch_time}", new_values={"mapped_employee": getattr(mapping.employee, "employee_code", "") if mapping else ""})
    return punch, created
