<?php

namespace App\Services;

use App\Models\AttendanceRecord;
use App\Models\AuditLog;
use App\Models\Employee;
use App\Models\LeaveBalance;
use App\Models\LeaveRequest;
use App\Models\LeaveType;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class LeaveManagementService
{
    public function __construct(
        protected OperationalNumberSequenceService $sequences,
        protected ErpNotificationService $notifications,
    ) {}

    public function create(Employee $employee, LeaveType $type, array $data, User $actor): LeaveRequest
    {
        $from = Carbon::parse($data['from_date'])->startOfDay();
        $to = Carbon::parse($data['to_date'])->startOfDay();
        $days = $from->diffInDays($to) + 1;

        if ($type->requires_attachment && empty($data['attachment_path'])) {
            throw ValidationException::withMessages(['attachment' => 'An attachment is required for this leave type.']);
        }
        if (LeaveRequest::where('employee_id', $employee->id)->whereNotIn('status', ['rejected', 'cancelled'])
            ->whereDate('from_date', '<=', $to)->whereDate('to_date', '>=', $from)->exists()) {
            throw ValidationException::withMessages(['from_date' => 'This employee already has a leave request covering part of the selected period.']);
        }

        if ($type->is_paid) {
            $balance = $this->balance($employee, $type, (int) $from->year);
            if ($balance->available < $days) {
                throw ValidationException::withMessages(['leave_type_id' => "Insufficient {$type->name} balance. Available: {$balance->available} day(s)."]);
            }
        }

        $request = LeaveRequest::create([
            'tenant_id' => $employee->tenant_id,
            'branch_id' => $employee->branch_id,
            'request_number' => $this->sequences->next($employee->tenant, 'leave'),
            'employee_id' => $employee->id,
            'leave_type_id' => $type->id,
            'from_date' => $from,
            'to_date' => $to,
            'total_days' => $days,
            'reason' => $data['reason'],
            'contact_during_leave' => $data['contact_during_leave'] ?? null,
            'attachment_path' => $data['attachment_path'] ?? null,
            'requested_by' => $actor->id,
            'status' => 'submitted',
        ]);

        AuditLog::record('leave', 'leave.requested', $request, null, $request->toArray(), $actor);
        $this->notifications->notifyPermission(
            $employee->tenant_id,
            'leave.approve',
            'approvals',
            'Leave approval required',
            $employee->full_name.' requested '.$days.' day(s) of '.$type->name.'.',
            route('app.leave.index', ['status' => 'submitted']),
            $actor->id,
        );

        return $request;
    }

    public function review(LeaveRequest $request, string $decision, ?string $comment, User $actor): LeaveRequest
    {
        if (! in_array($request->status, ['submitted', 'manager_approved'], true)) {
            throw ValidationException::withMessages(['leave' => 'This leave request is no longer pending review.']);
        }

        return DB::transaction(function () use ($request, $decision, $comment, $actor) {
            $request->loadMissing(['leaveType', 'employee']);
            if ($decision === 'rejected') {
                $request->update(['status' => 'rejected', 'rejected_by' => $actor->id, 'rejected_at' => now(), 'review_comment' => $comment]);
                AuditLog::record('leave', 'leave.rejected', $request, null, ['comment' => $comment], $actor);
                $this->notifyRequester($request, 'rejected');

                return $request;
            }

            if ($request->status === 'submitted' && $request->leaveType->approval_levels > 1) {
                $request->update(['status' => 'manager_approved', 'manager_approved_by' => $actor->id, 'manager_approved_at' => now(), 'current_approval_level' => 2, 'review_comment' => $comment]);
                AuditLog::record('leave', 'leave.manager_approved', $request, null, ['comment' => $comment], $actor);
                $this->notifications->notifyPermission($request->tenant_id, 'leave.approve', 'approvals', 'Leave requires HR approval', $request->employee->full_name.'\'s leave passed manager review.', route('app.leave.index', ['status' => 'manager_approved']), $actor->id);

                return $request;
            }

            $this->applyFinalApproval($request, $actor, $comment);

            return $request->fresh(['employee', 'leaveType']);
        });
    }

    public function cancel(LeaveRequest $request, User $actor): LeaveRequest
    {
        if (in_array($request->status, ['rejected', 'cancelled'], true)) {
            throw ValidationException::withMessages(['leave' => 'This leave request cannot be cancelled.']);
        }

        return DB::transaction(function () use ($request, $actor) {
            $request->loadMissing(['leaveType', 'employee']);
            if ($request->status === 'approved' && $request->leaveType->is_paid) {
                $balance = $this->balance($request->employee, $request->leaveType, (int) $request->from_date->year);
                $balance->update(['used' => max(0, (float) $balance->used - (float) $request->total_days)]);
            }
            AttendanceRecord::where('employee_id', $request->employee_id)->where('source', 'leave')
                ->whereBetween('attendance_date', [$request->from_date, $request->to_date])->delete();
            $request->update(['status' => 'cancelled']);
            AuditLog::record('leave', 'leave.cancelled', $request, null, null, $actor);

            return $request;
        });
    }

    public function balance(Employee $employee, LeaveType $type, int $year): LeaveBalance
    {
        return LeaveBalance::firstOrCreate(
            ['tenant_id' => $employee->tenant_id, 'employee_id' => $employee->id, 'leave_type_id' => $type->id, 'year' => $year],
            ['opening_balance' => $type->annual_allocation, 'accrued' => 0, 'used' => 0, 'adjusted' => 0],
        );
    }

    private function applyFinalApproval(LeaveRequest $request, User $actor, ?string $comment): void
    {
        if ($request->leaveType->is_paid) {
            $balance = $this->balance($request->employee, $request->leaveType, (int) $request->from_date->year);
            if ($balance->available < (float) $request->total_days) {
                throw ValidationException::withMessages(['leave' => 'The leave balance changed and is no longer sufficient.']);
            }
            $balance->increment('used', (float) $request->total_days);
        }

        foreach (CarbonPeriod::create($request->from_date, $request->to_date) as $date) {
            $existing = AttendanceRecord::where('employee_id', $request->employee_id)->whereDate('attendance_date', $date)->first();
            if ($existing && ($existing->actual_check_in || $existing->actual_check_out)) {
                throw ValidationException::withMessages(['leave' => 'Attendance with recorded work already exists on '.$date->format('d M Y').'. Resolve it before approving leave.']);
            }
            AttendanceRecord::updateOrCreate(
                ['tenant_id' => $request->tenant_id, 'employee_id' => $request->employee_id, 'attendance_date' => $date->toDateString()],
                ['branch_id' => $request->employee->branch_id, 'status' => 'on_leave', 'source' => 'leave', 'notes' => $request->request_number, 'created_by' => $actor->id, 'approved_by' => $actor->id, 'approved_at' => now()],
            );
        }

        $request->update(['status' => 'approved', 'hr_approved_by' => $actor->id, 'hr_approved_at' => now(), 'current_approval_level' => $request->leaveType->approval_levels, 'review_comment' => $comment]);
        AuditLog::record('leave', 'leave.approved', $request, null, ['comment' => $comment], $actor);
        $this->notifyRequester($request, 'approved');
    }

    private function notifyRequester(LeaveRequest $request, string $status): void
    {
        $this->notifications->notifyUser(
            $request->requester,
            'approvals',
            'Leave request '.$status,
            $request->request_number.' was '.$status.'.',
            route('app.leave.index', ['employee_id' => $request->employee_id]),
        );
    }
}
