<?php

namespace App\Services;

use App\Models\Account;
use App\Models\EmployeeAttendance;
use App\Models\EmployeePayroll;
use App\Models\Incentive;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PayrollService
{
    protected $numberGenerationService;

    public function __construct(NumberGenerationService $numberGenerationService)
    {
        $this->numberGenerationService = $numberGenerationService;
    }

    /**
     * Calculate payroll for an employee for a given period.
     *
     * @param int $employeeId
     * @param string $periodStart (Y-m-d)
     * @param string $periodEnd (Y-m-d)
     * @return EmployeePayroll
     * @throws \Exception
     */
    public function calculatePayroll(int $employeeId, string $periodStart, string $periodEnd): EmployeePayroll
    {
        $employee = Account::where('id', $employeeId)
            ->where('model_type', 'employee')
            ->firstOrFail();

        if (!$employee->is_active) {
            throw new \Exception("Employee {$employee->name} is not active.");
        }

        $startDate = Carbon::parse($periodStart);
        $endDate = Carbon::parse($periodEnd);

        // Get attendance summary for the period
        $attendanceSummary = $this->getAttendanceSummary($employeeId, $startDate, $endDate);

        // Determine pay type from employee record
        $payType = $this->mapWageTypeToPayType($employee->wage_type);

        // Calculate base amount
        $baseAmount = $this->calculateBaseAmount($employee, $attendanceSummary, $payType);

        // Get incentives
        $incentivesAmount = $this->calculateIncentives($employeeId);

        // Calculate deductions (based on absences for monthly employees)
        $deductions = $this->calculateDeductions($employee, $attendanceSummary, $payType);

        // Calculate gross and net
        $grossSalary = $baseAmount + $incentivesAmount;
        $netSalary = $grossSalary - $deductions;

        // Generate payroll code
        $payrollCode = $this->generatePayrollCode($employee);

        // Create payroll record
        $payroll = new EmployeePayroll([
            'payroll_code' => $payrollCode,
            'employee_id' => $employeeId,
            'period_start' => $startDate->format('Y-m-d'),
            'period_end' => $endDate->format('Y-m-d'),
            'pay_type' => $payType,
            'days_present' => $attendanceSummary['present'],
            'days_absent' => $attendanceSummary['absent'],
            'days_half' => $attendanceSummary['half_day'],
            'days_leave' => $attendanceSummary['leave'],
            'days_holiday' => $attendanceSummary['holiday'],
            'total_working_hours' => $attendanceSummary['total_hours'],
            'base_amount' => $baseAmount,
            'incentives' => $incentivesAmount,
            'deductions' => $deductions,
            'adjustments' => 0,
            'gross_salary' => $grossSalary,
            'net_salary' => $netSalary,
            'status' => EmployeePayroll::STATUS_CALCULATED,
            'created_by' => auth()->id(),
        ]);

        $payroll->save();

        return $payroll;
    }

    /**
     * Recalculate an existing payroll.
     *
     * @param EmployeePayroll $payroll
     * @return EmployeePayroll
     * @throws \Exception
     */
    public function recalculatePayroll(EmployeePayroll $payroll): EmployeePayroll
    {
        if (!$payroll->canEdit()) {
            throw new \Exception("Payroll cannot be edited in {$payroll->status} status.");
        }

        $employee = $payroll->employee;
        $startDate = Carbon::parse($payroll->period_start);
        $endDate = Carbon::parse($payroll->period_end);

        // Get fresh attendance summary
        $attendanceSummary = $this->getAttendanceSummary($employee->id, $startDate, $endDate);

        // Recalculate base amount
        $baseAmount = $this->calculateBaseAmount($employee, $attendanceSummary, $payroll->pay_type);

        // Get incentives
        $incentivesAmount = $this->calculateIncentives($employee->id);

        // Calculate deductions
        $deductions = $this->calculateDeductions($employee, $attendanceSummary, $payroll->pay_type);

        // Update payroll
        $grossSalary = $baseAmount + $incentivesAmount + $payroll->adjustments;
        $netSalary = $grossSalary - $deductions;

        $payroll->update([
            'days_present' => $attendanceSummary['present'],
            'days_absent' => $attendanceSummary['absent'],
            'days_half' => $attendanceSummary['half_day'],
            'days_leave' => $attendanceSummary['leave'],
            'days_holiday' => $attendanceSummary['holiday'],
            'total_working_hours' => $attendanceSummary['total_hours'],
            'base_amount' => $baseAmount,
            'incentives' => $incentivesAmount,
            'deductions' => $deductions,
            'gross_salary' => $grossSalary,
            'net_salary' => $netSalary,
            'status' => EmployeePayroll::STATUS_CALCULATED,
        ]);

        return $payroll->fresh();
    }

    /**
     * Approve a payroll.
     *
     * @param EmployeePayroll $payroll
     * @return EmployeePayroll
     * @throws \Exception
     */
    public function approvePayroll(EmployeePayroll $payroll): EmployeePayroll
    {
        if ($payroll->status !== EmployeePayroll::STATUS_CALCULATED) {
            throw new \Exception("Only calculated payrolls can be approved.");
        }

        $payroll->update([
            'status' => EmployeePayroll::STATUS_APPROVED,
            'approved_by' => auth()->id(),
            'approved_at' => now(),
        ]);

        return $payroll->fresh();
    }

    /**
     * Mark a payroll as paid.
     *
     * @param EmployeePayroll $payroll
     * @param string $paymentMethod
     * @param string|null $paymentReference
     * @param string|null $paymentDate
     * @return EmployeePayroll
     * @throws \Exception
     */
    public function markAsPaid(
        EmployeePayroll $payroll,
        string $paymentMethod,
        ?string $paymentReference = null,
        ?string $paymentDate = null
    ): EmployeePayroll {
        if ($payroll->status !== EmployeePayroll::STATUS_APPROVED) {
            throw new \Exception("Only approved payrolls can be marked as paid.");
        }

        $payroll->update([
            'status' => EmployeePayroll::STATUS_PAID,
            'payment_method' => $paymentMethod,
            'payment_reference' => $paymentReference,
            'payment_date' => $paymentDate ?? now()->format('Y-m-d'),
            'paid_by' => auth()->id(),
            'paid_at' => now(),
        ]);

        return $payroll->fresh();
    }

    /**
     * Get attendance summary for an employee in a date range.
     *
     * @param int $employeeId
     * @param Carbon $startDate
     * @param Carbon $endDate
     * @return array
     */
    protected function getAttendanceSummary(int $employeeId, Carbon $startDate, Carbon $endDate): array
    {
        $attendances = EmployeeAttendance::where('employee_id', $employeeId)
            ->whereBetween('attendance_date', [$startDate, $endDate])
            ->get();

        $summary = [
            'present' => 0,
            'absent' => 0,
            'half_day' => 0,
            'leave' => 0,
            'holiday' => 0,
            'total_hours' => 0,
        ];

        foreach ($attendances as $attendance) {
            switch ($attendance->status) {
                case 'present':
                    $summary['present']++;
                    break;
                case 'absent':
                    $summary['absent']++;
                    break;
                case 'half_day':
                    $summary['half_day']++;
                    break;
                case 'leave':
                    $summary['leave']++;
                    break;
                case 'holiday':
                    $summary['holiday']++;
                    break;
            }

            $summary['total_hours'] += $attendance->hours_worked ?? 0;
        }

        return $summary;
    }

    /**
     * Calculate base amount based on pay type.
     *
     * @param Account $employee
     * @param array $attendanceSummary
     * @param string $payType
     * @return float
     */
    protected function calculateBaseAmount(Account $employee, array $attendanceSummary, string $payType): float
    {
        if ($payType === 'daily') {
            // Daily wagers: rate × (present days + half days × 0.5)
            $dailyRate = $employee->daily_wage ?? 0;
            $workingDays = $attendanceSummary['present'] + ($attendanceSummary['half_day'] * 0.5);
            return $dailyRate * $workingDays;
        }

        // Monthly: return full monthly salary (deductions handled separately)
        return $employee->basic_salary ?? 0;
    }

    /**
     * Calculate total incentives for an employee.
     *
     * @param int $employeeId
     * @return float
     */
    protected function calculateIncentives(int $employeeId): float
    {
        return Incentive::where('employee_id', $employeeId)->sum('amount');
    }

    /**
     * Calculate deductions based on pay type and absences.
     *
     * @param Account $employee
     * @param array $attendanceSummary
     * @param string $payType
     * @return float
     */
    protected function calculateDeductions(Account $employee, array $attendanceSummary, string $payType): float
    {
        // For daily employees, no deductions since base amount is already based on attendance
        if ($payType === 'daily') {
            return 0;
        }

        // For monthly employees, deduct per-day amount for absences
        $monthlySalary = $employee->basic_salary ?? 0;
        $totalAbsences = $attendanceSummary['absent'];

        // Assuming 30 days per month for calculation
        $perDayAmount = $monthlySalary / 30;

        return $perDayAmount * $totalAbsences;
    }

    /**
     * Map wage_type to pay_type.
     *
     * @param string|null $wageType
     * @return string
     */
    protected function mapWageTypeToPayType(?string $wageType): string
    {
        return match($wageType) {
            'daily_wager' => 'daily',
            'salary', 'contract' => 'monthly',
            default => 'monthly',
        };
    }

    /**
     * Generate payroll code.
     *
     * @param Account $employee
     * @return string
     */
    protected function generatePayrollCode(Account $employee): string
    {
        // Format: PAYROLL-YYYY-XXXXX
        $year = now()->year;
        $lastPayroll = EmployeePayroll::whereYear('created_at', $year)
            ->orderBy('id', 'desc')
            ->first();

        $nextNumber = $lastPayroll ? ((int) substr($lastPayroll->payroll_code, -5)) + 1 : 1;

        return sprintf('PAYROLL-%s-%05d', $year, $nextNumber);
    }

    /**
     * Bulk calculate payroll for multiple employees.
     *
     * @param array $employeeIds
     * @param string $periodStart
     * @param string $periodEnd
     * @return array
     */
    public function bulkCalculatePayroll(array $employeeIds, string $periodStart, string $periodEnd): array
    {
        $results = [
            'success' => [],
            'failed' => [],
        ];

        foreach ($employeeIds as $employeeId) {
            try {
                $payroll = $this->calculatePayroll($employeeId, $periodStart, $periodEnd);
                $results['success'][] = [
                    'employee_id' => $employeeId,
                    'payroll_id' => $payroll->id,
                    'net_salary' => $payroll->net_salary,
                ];
            } catch (\Exception $e) {
                $results['failed'][] = [
                    'employee_id' => $employeeId,
                    'error' => $e->getMessage(),
                ];
                Log::error("Payroll calculation failed for employee {$employeeId}: " . $e->getMessage());
            }
        }

        return $results;
    }
}
