<?php

namespace App\Services\Employee;

use App\Services\BaseService;
use App\Repositories\LeaveRepository;
use App\Models\EmployeeLeave;
use App\Models\EmployeeLeaveBalance;
use App\Models\EmployeeDetails;
use App\Models\Holiday;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class LeaveService extends BaseService
{
    protected LeaveRepository $leaveRepository;

    public function __construct(LeaveRepository $repository)
    {
        parent::__construct($repository);
        $this->leaveRepository = $repository;
    }

    /**
     * Get leaves with employee details
     */
    public function getAllWithEmployee(): Collection
    {
        return $this->leaveRepository->getWithEmployee();
    }

    /**
     * Get leaves by employee
     */
    public function getByEmployee(int $employeeId): Collection
    {
        return $this->leaveRepository->getByEmployee($employeeId);
    }

    /**
     * Get pending leaves
     */
    public function getPending(): Collection
    {
        return $this->leaveRepository->getPending();
    }

    /**
     * Get pending leaves by department
     */
    public function getPendingByDepartment(string $department): Collection
    {
        return $this->leaveRepository->getPendingByDepartment($department);
    }

    /**
     * Get leaves for date range
     */
    public function getByDateRange(string $start, string $end): Collection
    {
        return $this->leaveRepository->getByDateRange($start, $end);
    }

    /**
     * Apply for leave
     */
    public function applyLeave(array $data): EmployeeLeave
    {
        return DB::transaction(function () use ($data) {

            // 🔹 Fetch employee details
            $employee = EmployeeDetails::findOrFail($data['employee_id']);

            // 🔹 Inject required employee fields
            $data['employee_name'] = $employee->employee_name ?? $employee->name;
            $data['department'] = $employee->department ?? null;

            // 🔹 Calculate working days if not provided
            if (empty($data['number_of_days'])) {
                $from = Carbon::parse($data['leave_from']);
                $to = Carbon::parse($data['leave_to']);
                $holidays = EmployeeLeave::getHolidaysInRange($from, $to);

                $data['number_of_days'] =
                    EmployeeLeave::calculateWorkingDays($from, $to, $holidays);
            }

            // 🔹 Set default values
            $data['status'] = EmployeeLeave::STATUS_PENDING;
            $data['requested_on'] = now();

            // 🔹 Prevent overlapping leaves
            if ($this->leaveRepository->hasOverlappingLeave(
                $data['employee_id'],
                $data['leave_from'],
                $data['leave_to']
            )) {
                throw new \Exception('You already have a leave request for this date range.');
            }

            $this->logAction('Leave application submitted', [
                'employee_id' => $data['employee_id'],
                'dates' => "{$data['leave_from']} to {$data['leave_to']}",
            ]);

            return $this->create($data);
        });
    }

    /**
     * Approve a leave request
     */
    public function approveLeave(int $id, ?string $remarks = null): EmployeeLeave
    {
        return DB::transaction(function () use ($id, $remarks) {
            $leave = $this->findByIdOrFail($id);

            if (!$leave->isPending()) {
                throw new \Exception('Only pending leaves can be approved.');
            }

            // Deduct from leave balance if CL
            if ($leave->leave_type === EmployeeLeave::TYPE_CL) {
                $year = Carbon::parse($leave->leave_from)->year;
                $success = EmployeeLeaveBalance::useLeave(
                    $leave->employee_id,
                    $year,
                    $leave->number_of_days,
                    EmployeeLeave::TYPE_CL
                );

                if (!$success) {
                    // Mark excess as LOP
                    $leave->lop_days = $leave->number_of_days;
                }
            }

            $leave->approve($remarks);

            $this->logAction('Leave approved', [
                'leave_id' => $id,
                'employee_id' => $leave->employee_id,
            ]);

            return $leave->fresh();
        });
    }

    /**
     * Reject a leave request
     */
    public function rejectLeave(int $id, ?string $reason = null): EmployeeLeave
    {
        return DB::transaction(function () use ($id, $reason) {
            $leave = $this->findByIdOrFail($id);

            if (!$leave->isPending()) {
                throw new \Exception('Only pending leaves can be rejected.');
            }

            $leave->reject($reason);

            $this->logAction('Leave rejected', [
                'leave_id' => $id,
                'employee_id' => $leave->employee_id,
                'remarks' => $reason,
            ]);

            return $leave->fresh();
        });
    }

    /**
     * Cancel a leave request
     */
    public function cancelLeave(int $id): EmployeeLeave
    {
        return DB::transaction(function () use ($id) {
            $leave = $this->findByIdOrFail($id);

            if ($leave->isApproved()) {
                // Restore leave balance if was deducted
                if ($leave->leave_type === EmployeeLeave::TYPE_CL && $leave->cl_days > 0) {
                    $year = Carbon::parse($leave->leave_from)->year;
                    EmployeeLeaveBalance::restoreLeave(
                        $leave->employee_id,
                        $year,
                        $leave->cl_days,
                        EmployeeLeave::TYPE_CL
                    );
                }
            }

            $leave->update(['status' => EmployeeLeave::STATUS_CANCELLED]);

            $this->logAction('Leave cancelled', [
                'leave_id' => $id,
                'employee_id' => $leave->employee_id,
            ]);

            return $leave->fresh();
        });
    }

    /**
     * Get leave balance for employee
     */
    public function getLeaveBalance(int $employeeId, int $year): array
    {
        return [
            'CL' => EmployeeLeaveBalance::getAvailableBalance($employeeId, $year, 'CL'),
            'SL' => EmployeeLeaveBalance::getAvailableBalance($employeeId, $year, 'SL'),
            'PL' => EmployeeLeaveBalance::getAvailableBalance($employeeId, $year, 'PL'),
        ];
    }

    /**
     * Get leave summary for employee for a year
     */
    public function getYearlySummary(int $employeeId, int $year): array
    {
        $leaves = $this->leaveRepository->getByYear($employeeId, $year);

        $summary = [
            'total_leaves' => $leaves->count(),
            'total_days' => $leaves->sum('number_of_days'),
            'approved' => $leaves->where('status', EmployeeLeave::STATUS_APPROVED)->count(),
            'pending' => $leaves->where('status', EmployeeLeave::STATUS_PENDING)->count(),
            'rejected' => $leaves->where('status', EmployeeLeave::STATUS_REJECTED)->count(),
            'by_type' => [],
        ];

        foreach (EmployeeLeave::getLeaveTypes() as $type) {
            $typeLeaves = $leaves->where('leave_type', $type);
            $summary['by_type'][$type] = [
                'count' => $typeLeaves->count(),
                'days' => $typeLeaves->sum('number_of_days'),
            ];
        }

        return $summary;
    }

    /**
     * Check if employee has overlapping leave
     */
    public function hasOverlappingLeave(int $employeeId, string $from, string $to, ?int $excludeId = null): bool
    {
        return $this->leaveRepository->hasOverlappingLeave($employeeId, $from, $to, $excludeId);
    }
}
