<?php

namespace App\Http\Controllers\Modules\Employee;

use App\Http\Controllers\Controller;
use App\Http\Traits\HasRoleViews;
use App\Models\EmployeeDetails;
use App\Models\EmployeePayroll;
use App\Models\EmployeeSalaryDetail;
use App\Models\EmployeeSalaryStructure;
use App\Services\Employee\PayrollService;
use App\Services\Employee\SalaryStructureService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;

/**
 * Unified Payroll Controller
 *
 * Handles payroll processing, salary details, and payslip generation.
 * Uses PayrollService for CRUD operations and SalaryStructureService for salary calculations.
 */
class PayrollController extends Controller
{
    use HasRoleViews;

    protected PayrollService $payrollService;
    protected SalaryStructureService $salaryStructureService;

    public function __construct(
        PayrollService $payrollService,
        SalaryStructureService $salaryStructureService
    ) {
        $this->payrollService = $payrollService;
        $this->salaryStructureService = $salaryStructureService;
    }

    /**
     * Display payroll management page
     */
    public function index(Request $request)
    {
        $month = $request->input('month', now()->format('Y-m'));
        $status = $request->input('status');

        $payrolls = $this->payrollService->getByMonth($month);

        if ($status) {
            $payrolls = $payrolls->where('status', $status);
        }

        $employees = EmployeeDetails::where('status', EmployeeDetails::STATUS_ACTIVE)->get();

        $stats = [
            'total' => $payrolls->count(),
            'draft' => $payrolls->where('status', EmployeePayroll::STATUS_DRAFT)->count(),
            'pending' => $payrolls->where('status', EmployeePayroll::STATUS_PENDING)->count(),
            'approved' => $payrolls->where('status', EmployeePayroll::STATUS_APPROVED)->count(),
            'paid' => $payrolls->where('status', EmployeePayroll::STATUS_PAID)->count(),
            'total_gross' => $payrolls->sum('gross_salary'),
            'total_net' => $payrolls->sum('net_salary'),
        ];

        return $this->roleView('employee.payroll', compact('payrolls', 'employees', 'month', 'stats'));
    }

    /**
     * Get payroll data for DataTable
     */
    public function list(Request $request): JsonResponse
    {
        $month = $request->input('month');

        if ($month) {
            $payrolls = $this->payrollService->getByMonth($month);
        } else {
            $payrolls = $this->payrollService->getAllWithEmployee();
        }

        return response()->json(['data' => $payrolls]);
    }

    /**
     * Generate payroll for an employee
     */
    public function generate(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'employee_id' => 'required|exists:employees,id',
            'month_year' => 'required|date_format:Y-m',
            'days_worked' => 'required|integer|min:0|max:31',
            'ot_hours' => 'nullable|numeric|min:0',
            'deductions' => 'nullable|numeric|min:0',
            'bonus' => 'nullable|numeric|min:0',
            'remarks' => 'nullable|string|max:500',
        ]);

        try {
            // Check if payroll already exists
            $existing = EmployeePayroll::where('employee_id', $validated['employee_id'])
                ->where('month_year', $validated['month_year'])
                ->first();

            if ($existing) {
                return response()->json([
                    'success' => false,
                    'message' => 'Payroll already exists for this employee and month',
                ], 400);
            }

            // Get salary structure
            $structure = $this->salaryStructureService->getActiveForEmployee($validated['employee_id']);

            if (!$structure) {
                return response()->json([
                    'success' => false,
                    'message' => 'No salary structure found for this employee',
                ], 400);
            }

            // Calculate salary
            $monthDate = Carbon::parse($validated['month_year'] . '-01');
            $totalDaysInMonth = $monthDate->daysInMonth;

            $breakdown = $this->salaryStructureService->calculateSalaryForDays(
                $validated['employee_id'],
                $validated['days_worked'],
                $totalDaysInMonth
            );

            $grossSalary = $breakdown['calculated']['gross'];
            $deductions = $breakdown['calculated']['deductions'] + ($validated['deductions'] ?? 0);
            $bonus = $validated['bonus'] ?? 0;
            $netSalary = $grossSalary - $deductions + $bonus;

            // Create payroll record
            $payroll = EmployeePayroll::create([
                'employee_id' => $validated['employee_id'],
                'month_year' => $validated['month_year'],
                'days_worked' => $validated['days_worked'],
                'total_days' => $totalDaysInMonth,
                'basic_da' => $breakdown['calculated']['basic_da'],
                'hra' => $breakdown['calculated']['hra'],
                'conveyance' => $breakdown['calculated']['conveyance'],
                'washing_allowance' => $breakdown['calculated']['washing_allowance'],
                'ot_hours' => $validated['ot_hours'] ?? 0,
                'ot_amount' => ($structure->incentive_per_hour ?? 0) * ($validated['ot_hours'] ?? 0),
                'gross_salary' => $grossSalary + $bonus,
                'pf_deduction' => $structure->pf_percentage ? ($grossSalary * $structure->pf_percentage / 100) : 0,
                'esi_deduction' => $structure->esi_percentage ? ($grossSalary * $structure->esi_percentage / 100) : 0,
                'pt_deduction' => $structure->pt_percentage ? ($grossSalary * $structure->pt_percentage / 100) : 0,
                'other_deductions' => $validated['deductions'] ?? 0,
                'total_deductions' => $deductions,
                'bonus' => $bonus,
                'net_salary' => $netSalary,
                'status' => EmployeePayroll::STATUS_DRAFT,
                'remarks' => $validated['remarks'],
            ]);

            return response()->json([
                'success' => true,
                'message' => 'Payroll generated successfully',
                'data' => $payroll->load('employee'),
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to generate payroll: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to generate payroll: ' . $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Bulk generate payroll for all employees
     */
    public function bulkGenerate(Request $request): JsonResponse
    {
        if (!$this->isSuperAdmin()) {
            return response()->json([
                'success' => false,
                'message' => 'Unauthorized',
            ], 403);
        }

        $validated = $request->validate([
            'month_year' => 'required|date_format:Y-m',
        ]);

        try {
            $monthDate = Carbon::parse($validated['month_year'] . '-01');
            $totalDaysInMonth = $monthDate->daysInMonth;

            $employees = EmployeeDetails::where('status', EmployeeDetails::STATUS_ACTIVE)->get();

            $generated = 0;
            $skipped = 0;
            $errors = [];

            DB::transaction(function () use ($employees, $validated, $totalDaysInMonth, &$generated, &$skipped, &$errors) {
                foreach ($employees as $employee) {
                    // Skip if payroll already exists
                    $existing = EmployeePayroll::where('employee_id', $employee->id)
                        ->where('month_year', $validated['month_year'])
                        ->exists();

                    if ($existing) {
                        $skipped++;
                        continue;
                    }

                    // Get salary structure
                    $structure = $this->salaryStructureService->getActiveForEmployee($employee->id);

                    if (!$structure) {
                        $errors[] = "Employee {$employee->employee_id}: No salary structure";
                        continue;
                    }

                    // Calculate with full month assumption
                    $breakdown = $this->salaryStructureService->calculateSalaryForDays(
                        $employee->id,
                        $totalDaysInMonth,
                        $totalDaysInMonth
                    );

                    $grossSalary = $breakdown['calculated']['gross'];
                    $deductions = $breakdown['calculated']['deductions'];
                    $netSalary = $breakdown['calculated']['net'];

                    EmployeePayroll::create([
                        'employee_id' => $employee->id,
                        'month_year' => $validated['month_year'],
                        'days_worked' => $totalDaysInMonth,
                        'total_days' => $totalDaysInMonth,
                        'basic_da' => $breakdown['calculated']['basic_da'],
                        'hra' => $breakdown['calculated']['hra'],
                        'conveyance' => $breakdown['calculated']['conveyance'],
                        'washing_allowance' => $breakdown['calculated']['washing_allowance'],
                        'gross_salary' => $grossSalary,
                        'pf_deduction' => $structure->pf_percentage ? ($grossSalary * $structure->pf_percentage / 100) : 0,
                        'esi_deduction' => $structure->esi_percentage ? ($grossSalary * $structure->esi_percentage / 100) : 0,
                        'pt_deduction' => $structure->pt_percentage ? ($grossSalary * $structure->pt_percentage / 100) : 0,
                        'total_deductions' => $deductions,
                        'net_salary' => $netSalary,
                        'status' => EmployeePayroll::STATUS_DRAFT,
                    ]);

                    $generated++;
                }
            });

            $message = "Generated $generated payroll records.";
            if ($skipped > 0) {
                $message .= " Skipped $skipped (already exist).";
            }
            if (!empty($errors)) {
                Log::warning('Bulk payroll generation errors:', $errors);
            }

            return response()->json([
                'success' => true,
                'message' => $message,
                'generated' => $generated,
                'skipped' => $skipped,
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to bulk generate payroll: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to generate payroll',
            ], 500);
        }
    }

    /**
     * Get payroll for editing
     */
    public function edit(int $id): JsonResponse
    {
        try {
            $payroll = $this->payrollService->findByIdWith($id, ['employee']);

            return response()->json([
                'success' => true,
                'data' => $payroll,
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }
    }

    /**
     * Update payroll
     */
    public function update(Request $request, int $id): JsonResponse
    {
        $payroll = EmployeePayroll::find($id);

        if (!$payroll) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }

        if ($payroll->status === EmployeePayroll::STATUS_PAID) {
            return response()->json([
                'success' => false,
                'message' => 'Cannot update paid payroll',
            ], 400);
        }

        $validated = $request->validate([
            'days_worked' => 'required|integer|min:0|max:31',
            'ot_hours' => 'nullable|numeric|min:0',
            'other_deductions' => 'nullable|numeric|min:0',
            'bonus' => 'nullable|numeric|min:0',
            'remarks' => 'nullable|string|max:500',
        ]);

        try {
            // Recalculate
            $breakdown = $this->salaryStructureService->calculateSalaryForDays(
                $payroll->employee_id,
                $validated['days_worked'],
                $payroll->total_days
            );

            $structure = $this->salaryStructureService->getActiveForEmployee($payroll->employee_id);

            $grossSalary = $breakdown['calculated']['gross'] + ($validated['bonus'] ?? 0);
            $otAmount = ($structure?->incentive_per_hour ?? 0) * ($validated['ot_hours'] ?? 0);
            $grossSalary += $otAmount;

            $pfDeduction = $structure?->pf_percentage ? ($grossSalary * $structure->pf_percentage / 100) : 0;
            $esiDeduction = $structure?->esi_percentage ? ($grossSalary * $structure->esi_percentage / 100) : 0;
            $ptDeduction = $structure?->pt_percentage ? ($grossSalary * $structure->pt_percentage / 100) : 0;
            $totalDeductions = $pfDeduction + $esiDeduction + $ptDeduction + ($validated['other_deductions'] ?? 0);
            $netSalary = $grossSalary - $totalDeductions;

            $payroll->update([
                'days_worked' => $validated['days_worked'],
                'basic_da' => $breakdown['calculated']['basic_da'],
                'hra' => $breakdown['calculated']['hra'],
                'conveyance' => $breakdown['calculated']['conveyance'],
                'washing_allowance' => $breakdown['calculated']['washing_allowance'],
                'ot_hours' => $validated['ot_hours'] ?? 0,
                'ot_amount' => $otAmount,
                'gross_salary' => $grossSalary,
                'pf_deduction' => $pfDeduction,
                'esi_deduction' => $esiDeduction,
                'pt_deduction' => $ptDeduction,
                'other_deductions' => $validated['other_deductions'] ?? 0,
                'total_deductions' => $totalDeductions,
                'bonus' => $validated['bonus'] ?? 0,
                'net_salary' => $netSalary,
                'remarks' => $validated['remarks'],
            ]);

            return response()->json([
                'success' => true,
                'message' => 'Payroll updated successfully',
                'data' => $payroll->fresh()->load('employee'),
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to update payroll: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to update payroll',
            ], 500);
        }
    }

    /**
     * Submit payroll for approval
     */
    public function submit(int $id): JsonResponse
    {
        $payroll = EmployeePayroll::find($id);

        if (!$payroll) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }

        if ($payroll->status !== EmployeePayroll::STATUS_DRAFT) {
            return response()->json([
                'success' => false,
                'message' => 'Only draft payroll can be submitted',
            ], 400);
        }

        $payroll->submitForApproval();

        return response()->json([
            'success' => true,
            'message' => 'Payroll submitted for approval',
        ]);
    }

    /**
     * Approve payroll (SuperAdmin only)
     */
    public function approve(int $id): JsonResponse
    {
        if (!$this->isSuperAdmin()) {
            return response()->json([
                'success' => false,
                'message' => 'Unauthorized',
            ], 403);
        }

        $payroll = EmployeePayroll::find($id);

        if (!$payroll) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }

        if ($payroll->status !== EmployeePayroll::STATUS_PENDING) {
            return response()->json([
                'success' => false,
                'message' => 'Only pending payroll can be approved',
            ], 400);
        }

        $payroll->approve();

        return response()->json([
            'success' => true,
            'message' => 'Payroll approved',
        ]);
    }

    /**
     * Bulk approve payroll (SuperAdmin only)
     */
    public function bulkApprove(Request $request): JsonResponse
    {
        if (!$this->isSuperAdmin()) {
            return response()->json([
                'success' => false,
                'message' => 'Unauthorized',
            ], 403);
        }

        $validated = $request->validate([
            'ids' => 'required|array',
            'ids.*' => 'integer|exists:employee_payrolls,id',
        ]);

        try {
            $approved = 0;

            DB::transaction(function () use ($validated, &$approved) {
                foreach ($validated['ids'] as $id) {
                    $payroll = EmployeePayroll::find($id);
                    if ($payroll && $payroll->status === EmployeePayroll::STATUS_PENDING) {
                        $payroll->approve();
                        $approved++;
                    }
                }
            });

            return response()->json([
                'success' => true,
                'message' => "Approved $approved payroll records",
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to bulk approve: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to approve payroll',
            ], 500);
        }
    }

    /**
     * Mark payroll as paid (SuperAdmin only)
     */
    public function markPaid(Request $request, int $id): JsonResponse
    {
        if (!$this->isSuperAdmin()) {
            return response()->json([
                'success' => false,
                'message' => 'Unauthorized',
            ], 403);
        }

        $validated = $request->validate([
            'payment_date' => 'required|date',
            'payment_mode' => 'required|string|in:' . implode(',', [
                EmployeeSalaryDetail::PAYMENT_BANK,
                EmployeeSalaryDetail::PAYMENT_CASH,
                EmployeeSalaryDetail::PAYMENT_CHEQUE,
            ]),
            'reference_number' => 'nullable|string|max:100',
        ]);

        $payroll = EmployeePayroll::find($id);

        if (!$payroll) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }

        if ($payroll->status !== EmployeePayroll::STATUS_APPROVED) {
            return response()->json([
                'success' => false,
                'message' => 'Only approved payroll can be marked as paid',
            ], 400);
        }

        $payroll->markAsPaid(
            $validated['payment_date'],
            $validated['payment_mode'],
            $validated['reference_number'] ?? null
        );

        return response()->json([
            'success' => true,
            'message' => 'Payroll marked as paid',
        ]);
    }

    /**
     * Generate payslip PDF
     */
    public function payslip(int $id)
    {
        $payroll = EmployeePayroll::with(['employee', 'employee.salaryStructure'])
            ->findOrFail($id);

        $pdf = Pdf::loadView('pdf.payslip', compact('payroll'));
        $pdf->setPaper('a4', 'portrait');

        $fileName = "payslip_{$payroll->employee->employee_id}_{$payroll->month_year}.pdf";

        return $pdf->download($fileName);
    }

    /**
     * Print payslip
     */
    public function printPayslip(int $id)
    {
        $payroll = EmployeePayroll::with(['employee', 'employee.salaryStructure'])
            ->findOrFail($id);

        return $this->roleView('employee.payslip_print', compact('payroll'));
    }

    /**
     * Delete payroll
     */
    public function destroy(int $id): JsonResponse
    {
        try {
            $payroll = $this->payrollService->findByIdOrFail($id);

            if ($payroll->status === EmployeePayroll::STATUS_PAID) {
                return response()->json([
                    'success' => false,
                    'message' => 'Cannot delete paid payroll',
                ], 400);
            }

            $this->payrollService->delete($id);

            return response()->json([
                'success' => true,
                'message' => 'Payroll deleted successfully',
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Payroll not found',
            ], 404);
        }
    }
}
