<?php

namespace Modules\Leave\Services;

use App\Models\ActivityLogModel;
use App\Models\EmployeeModel;
use App\Models\LeaveBalanceModel;
use App\Models\LeaveRequestModel;
use App\Models\LeaveTypeModel;

class LeaveRequestService
{
    protected LeaveRequestModel $requests;
    protected LeaveBalanceModel $balances;
    protected LeaveTypeModel $types;
    protected EmployeeModel $employees;
    protected LeaveBalanceService $balanceService;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->requests       = new LeaveRequestModel();
        $this->balances       = new LeaveBalanceModel();
        $this->types          = new LeaveTypeModel();
        $this->employees      = new EmployeeModel();
        $this->balanceService = new LeaveBalanceService();
        $this->activity       = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $employeeId  = (int) ($input['employee_id'] ?? 0);
        $leaveTypeId = (int) ($input['leave_type_id'] ?? 0);
        $startDate   = $input['start_date'] ?? '';
        $endDate     = $input['end_date'] ?? '';

        $validation = $this->validateDates($startDate, $endDate);
        if (! $validation['success']) {
            return $validation;
        }

        if (! $this->employees->find($employeeId)) {
            return ['success' => false, 'errors' => ['employee_id' => 'Employee not found.']];
        }

        $type = $this->types->find($leaveTypeId);
        if (! $type || $type['status'] !== 'active') {
            return ['success' => false, 'errors' => ['leave_type_id' => 'Invalid leave type.']];
        }

        if ($this->requests->hasOverlap($employeeId, $startDate, $endDate)) {
            return ['success' => false, 'errors' => ['start_date' => 'This employee already has a leave request overlapping these dates.']];
        }

        $days = LeaveRequestModel::calculateDays($startDate, $endDate);
        $year = (int) date('Y', strtotime($startDate));

        if ((float) $type['days_per_year'] > 0) {
            $this->balanceService->ensureBalancesForEmployee($employeeId, $year);
            $balance = $this->balances->where('employee_id', $employeeId)
                ->where('leave_type_id', $leaveTypeId)
                ->where('year', $year)
                ->first();

            if ($balance && $this->balances->getRemaining($balance) < $days) {
                return ['success' => false, 'errors' => ['days' => 'Insufficient leave balance. Remaining: ' . $this->balances->getRemaining($balance) . ' days.']];
            }
        }

        $status = ! empty($type['requires_approval']) ? 'pending' : 'approved';

        $data = [
            'employee_id'   => $employeeId,
            'leave_type_id' => $leaveTypeId,
            'start_date'    => $startDate,
            'end_date'      => $endDate,
            'days'          => $days,
            'reason'        => $input['reason'] ?: null,
            'status'        => $status,
            'created_by'    => $actingUserId,
            'updated_by'    => $actingUserId,
        ];

        if ($status === 'approved') {
            $data['approved_by'] = $actingUserId;
            $data['approved_at'] = date('Y-m-d H:i:s');
        }

        if (! $this->requests->insert($data, true)) {
            return ['success' => false, 'errors' => $this->requests->errors()];
        }

        $id = $this->requests->getInsertID();

        if ($status === 'approved') {
            $this->deductBalance($employeeId, $leaveTypeId, $year, $days);
            $this->syncEmployeeLeaveStatus($employeeId);
        }

        $this->activity->log('leave_requests', 'created', $actingUserId, "Created leave request #{$id}", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function approve(int $id, ?int $actingUserId): array
    {
        $request = $this->requests->find($id);
        if (! $request) {
            return ['success' => false, 'error' => 'Leave request not found.'];
        }

        if ($request['status'] !== 'pending') {
            return ['success' => false, 'error' => 'Only pending requests can be approved.'];
        }

        $type = $this->types->find($request['leave_type_id']);
        $year = (int) date('Y', strtotime($request['start_date']));

        if ($type && (float) $type['days_per_year'] > 0) {
            $this->balanceService->ensureBalancesForEmployee((int) $request['employee_id'], $year);
            $balance = $this->balances->where('employee_id', $request['employee_id'])
                ->where('leave_type_id', $request['leave_type_id'])
                ->where('year', $year)
                ->first();

            if ($balance && $this->balances->getRemaining($balance) < (float) $request['days']) {
                return ['success' => false, 'error' => 'Insufficient leave balance to approve this request.'];
            }
        }

        $this->requests->update($id, [
            'status'      => 'approved',
            'approved_by' => $actingUserId,
            'approved_at' => date('Y-m-d H:i:s'),
            'updated_by'  => $actingUserId,
        ]);

        $this->deductBalance(
            (int) $request['employee_id'],
            (int) $request['leave_type_id'],
            $year,
            (float) $request['days']
        );
        $this->syncEmployeeLeaveStatus((int) $request['employee_id']);

        $this->activity->log('leave_requests', 'approved', $actingUserId, "Approved leave request #{$id}", (string) $id);

        return ['success' => true];
    }

    public function reject(int $id, string $reason, ?int $actingUserId): array
    {
        $request = $this->requests->find($id);
        if (! $request) {
            return ['success' => false, 'error' => 'Leave request not found.'];
        }

        if ($request['status'] !== 'pending') {
            return ['success' => false, 'error' => 'Only pending requests can be rejected.'];
        }

        $this->requests->update($id, [
            'status'           => 'rejected',
            'rejection_reason' => $reason ?: null,
            'approved_by'      => $actingUserId,
            'approved_at'      => date('Y-m-d H:i:s'),
            'updated_by'       => $actingUserId,
        ]);

        $this->activity->log('leave_requests', 'rejected', $actingUserId, "Rejected leave request #{$id}", (string) $id);

        return ['success' => true];
    }

    public function cancel(int $id, ?int $actingUserId): array
    {
        $request = $this->requests->find($id);
        if (! $request) {
            return ['success' => false, 'error' => 'Leave request not found.'];
        }

        if (! in_array($request['status'], ['pending', 'approved'], true)) {
            return ['success' => false, 'error' => 'This request cannot be cancelled.'];
        }

        $wasApproved = $request['status'] === 'approved';

        $this->requests->update($id, [
            'status'     => 'cancelled',
            'updated_by' => $actingUserId,
        ]);

        if ($wasApproved) {
            $year = (int) date('Y', strtotime($request['start_date']));
            $balance = $this->balances->where('employee_id', $request['employee_id'])
                ->where('leave_type_id', $request['leave_type_id'])
                ->where('year', $year)
                ->first();

            if ($balance) {
                $this->balances->subtractUsed((int) $balance['id'], (float) $request['days']);
            }

            $this->syncEmployeeLeaveStatus((int) $request['employee_id']);
        }

        $this->activity->log('leave_requests', 'cancelled', $actingUserId, "Cancelled leave request #{$id}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $request = $this->requests->find($id);
        if (! $request) {
            return ['success' => false, 'error' => 'Leave request not found.'];
        }

        if ($request['status'] === 'approved') {
            return ['success' => false, 'error' => 'Approved requests must be cancelled before deletion.'];
        }

        $this->requests->delete($id);
        $this->activity->log('leave_requests', 'deleted', $actingUserId, "Deleted leave request #{$id}", (string) $id);

        return ['success' => true];
    }

    protected function validateDates(string $startDate, string $endDate): array
    {
        if ($startDate === '' || $endDate === '') {
            return ['success' => false, 'errors' => ['start_date' => 'Start and end dates are required.']];
        }

        if (strtotime($endDate) < strtotime($startDate)) {
            return ['success' => false, 'errors' => ['end_date' => 'End date must be on or after start date.']];
        }

        return ['success' => true];
    }

    protected function deductBalance(int $employeeId, int $leaveTypeId, int $year, float $days): void
    {
        $balance = $this->balances->where('employee_id', $employeeId)
            ->where('leave_type_id', $leaveTypeId)
            ->where('year', $year)
            ->first();

        if ($balance) {
            $this->balances->addUsed((int) $balance['id'], $days);
        }
    }

    protected function syncEmployeeLeaveStatus(int $employeeId): void
    {
        $today = date('Y-m-d');
        $onLeave = $this->requests->where('employee_id', $employeeId)
            ->where('status', 'approved')
            ->where('start_date <=', $today)
            ->where('end_date >=', $today)
            ->countAllResults() > 0;

        $employee = $this->employees->find($employeeId);
        if (! $employee) {
            return;
        }

        if ($onLeave && $employee['status'] === 'active') {
            $this->employees->update($employeeId, ['status' => 'on_leave']);
        } elseif (! $onLeave && $employee['status'] === 'on_leave') {
            $this->employees->update($employeeId, ['status' => 'active']);
        }
    }
}
