<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Jobs\GeneratePayrollJob;
use App\Models\CompanySetting;
use App\Models\Department;
use App\Models\Payroll;
use App\Models\SalaryGroup;
use App\Models\User;
use App\Services\PayrollService;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;

class PayrollController extends Controller
{
    private function companyId(): ?int
    {
        return auth()->user()?->company_id;
    }

    private function getPayrollSetting(string $key, $default = null)
    {
        if (! $this->companyId()) {
            return $default;
        }
        $s = CompanySetting::withoutGlobalScopes()->where('company_id', $this->companyId())->where('key', $key)->first();
        return $s ? $s->value : $default;
    }

    private function setPayrollSetting(string $key, ?string $value): void
    {
        if (! $this->companyId()) {
            return;
        }
        CompanySetting::withoutGlobalScopes()->updateOrCreate(
            ['company_id' => $this->companyId(), 'key' => $key],
            ['value' => $value ?? '']
        );
    }

    public function index(Request $request)
    {
        $companyId = auth()->user()->company_id;
        $year = $request->filled('year') ? (int) $request->year : (int) date('Y');
        $month = $request->filled('month') ? (int) $request->month : (int) date('n');
        $year = max(2020, min(2030, $year));
        $month = max(1, min(12, $month));

        $monthStatuses = $this->getMonthStatuses($companyId, $year);

        $baseQuery = Payroll::with(['user.department', 'payrollComponents'])
            ->where('company_id', $companyId)
            ->where('year', $year)
            ->where('month', $month);

        if ($request->filled('search')) {
            $term = $request->search;
            $baseQuery->whereHas('user', function ($q) use ($term) {
                $q->where('name', 'like', '%' . $term . '%')
                    ->orWhere('email', 'like', '%' . $term . '%');
            });
        }
        if ($request->filled('department')) {
            $baseQuery->whereHas('user', fn ($q) => $q->where('department_id', $request->department));
        }
        if ($request->filled('status') && in_array($request->status, ['paid', 'pending'], true)) {
            if ($request->status === 'paid') {
                $baseQuery->where('status', 'paid');
            } else {
                $baseQuery->where(function ($q) {
                    $q->whereNull('status')->orWhere('status', '!=', 'paid');
                });
            }
        }

        $payrolls = $baseQuery->orderBy('user_id')->paginate(20)->withQueryString();

        $summaryQuery = Payroll::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('year', $year)
            ->where('month', $month);
        $allForMonth = (clone $summaryQuery)->get();
        $summary = [
            'total_employees' => $allForMonth->count(),
            'processed' => $allForMonth->where('status', 'paid')->count(),
            'pending' => $allForMonth->filter(fn ($p) => ($p->status ?? '') !== 'paid')->count(),
            'total_payroll' => $allForMonth->sum('net_salary'),
        ];

        $departments = Department::where('company_id', $companyId)->orderBy('name')->get();

        $filters = $request->only('search', 'department', 'status');

        return view('payroll.index', compact('payrolls', 'year', 'month', 'monthStatuses', 'summary', 'departments', 'filters'));
    }

    /** @return array<int, 'completed'|'pending'|'future'> */
    private function getMonthStatuses(?int $companyId, int $year): array
    {
        $currentYear = (int) date('Y');
        $currentMonth = (int) date('n');
        $statuses = [];
        for ($m = 1; $m <= 12; $m++) {
            if ($year > $currentYear || ($year === $currentYear && $m > $currentMonth)) {
                $statuses[$m] = 'future';
                continue;
            }
            $count = Payroll::withoutGlobalScopes()
                ->where('company_id', $companyId)
                ->where('year', $year)
                ->where('month', $m)
                ->count();
            $paidCount = Payroll::withoutGlobalScopes()
                ->where('company_id', $companyId)
                ->where('year', $year)
                ->where('month', $m)
                ->where('status', 'paid')
                ->count();
            if ($count > 0 && $paidCount === $count) {
                $statuses[$m] = 'completed';
            } else {
                $statuses[$m] = 'pending';
            }
        }
        return $statuses;
    }

    public function markAllPaid(Request $request)
    {
        $request->validate([
            'year' => 'required|integer|min:2020|max:2030',
            'month' => 'required|integer|min:1|max:12',
        ]);
        $companyId = $this->companyId();
        if (! $companyId) {
            return redirect()->route('payroll.index')->with('error', 'Company not found.');
        }
        $updated = Payroll::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('year', $request->year)
            ->where('month', $request->month)
            ->update(['status' => 'paid', 'payment_date' => now()]);
        return redirect()->route('payroll.index', [
            'year' => $request->year,
            'month' => $request->month,
        ])->with('success', $updated . ' payroll record(s) marked as paid.');
    }

    public function export(Request $request): StreamedResponse
    {
        $companyId = $this->companyId();
        $year = $request->filled('year') ? (int) $request->year : (int) date('Y');
        $month = $request->filled('month') ? (int) $request->month : (int) date('n');

        $query = Payroll::with(['user.department', 'payrollComponents'])
            ->where('company_id', $companyId)
            ->where('year', $year)
            ->where('month', $month);

        if ($request->filled('search')) {
            $term = $request->search;
            $query->whereHas('user', function ($q) use ($term) {
                $q->where('name', 'like', '%' . $term . '%')
                    ->orWhere('email', 'like', '%' . $term . '%');
            });
        }
        if ($request->filled('department')) {
            $query->whereHas('user', fn ($q) => $q->where('department_id', $request->department));
        }
        if ($request->filled('status') && in_array($request->status, ['paid', 'pending'], true)) {
            if ($request->status === 'paid') {
                $query->where('status', 'paid');
            } else {
                $query->where(fn ($q) => $q->whereNull('status')->orWhere('status', '!=', 'paid'));
            }
        }

        $payrolls = $query->orderBy('user_id')->get();

        $filename = 'payroll-' . $year . '-' . str_pad((string) $month, 2, '0', STR_PAD_LEFT) . '.csv';

        return response()->streamDownload(function () use ($payrolls) {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['Employee ID', 'Name', 'Department', 'Basic Salary', 'Allowances', 'Deductions', 'Net Salary', 'Status']);
            foreach ($payrolls as $p) {
                $allowances = $p->payrollComponents->where('is_earning', true)->sum('amount') - (float) ($p->basic_salary ?? 0);
                if ($allowances < 0) {
                    $allowances = 0;
                }
                $deductions = $p->payrollComponents->where('is_earning', false)->sum(fn ($c) => abs((float) $c->amount));
                $empId = $p->user?->employee_number ?? 'EMP' . str_pad((string) ($p->user_id ?? 0), 3, '0', STR_PAD_LEFT);
                fputcsv($out, [
                    $empId,
                    $p->user?->name ?? '—',
                    $p->user?->department?->name ?? '—',
                    number_format((float) ($p->basic_salary ?? 0), 2),
                    number_format($allowances, 2),
                    number_format($deductions, 2),
                    number_format((float) ($p->net_salary ?? 0), 2),
                    $p->status ?? 'pending',
                ]);
            }
            fclose($out);
        }, $filename, [
            'Content-Type' => 'text/csv',
        ]);
    }

    public function settingsIndex()
    {
        $settings = [
            'payroll_pf_enabled' => $this->getPayrollSetting('payroll_pf_enabled', '0'),
            'pf_excluded_in_ctc' => $this->getPayrollSetting('pf_excluded_in_ctc', '0'),
            'payroll_pf_rate' => $this->getPayrollSetting('payroll_pf_rate', '12'),
            'payroll_pf_cap' => $this->getPayrollSetting('payroll_pf_cap', '15000'),
            'payroll_esi_enabled' => $this->getPayrollSetting('payroll_esi_enabled', '0'),
            'esi_excluded_in_ctc' => $this->getPayrollSetting('esi_excluded_in_ctc', '0'),
            'payroll_esi_rate' => $this->getPayrollSetting('payroll_esi_rate', '0.75'),
            'payroll_esi_cap' => $this->getPayrollSetting('payroll_esi_cap', '21000'),
            'payroll_pt_enabled' => $this->getPayrollSetting('payroll_pt_enabled', '0'),
            'payroll_pt_amount' => $this->getPayrollSetting('payroll_pt_amount', '200'),
        ];
        return view('payroll.settings', compact('settings'));
    }

    public function settingsStore(Request $request)
    {
        $request->validate([
            // Payroll policy
            'payroll_currency' => 'nullable|string|max:10',
            'payroll_pay_day' => 'nullable',
            'payroll_rounding_mode' => 'nullable|in:nearest_rupee,nearest_0_05',
            'payroll_lop_basis' => 'nullable|in:gross,basic,earned_gross',
            'payroll_working_days_source' => 'nullable|in:weekday_minus_holidays,shift_calendar',
            // Declaration
            'payroll_declaration_window_enabled' => 'nullable|in:0,1',
            'declaration_from_day' => 'nullable|integer|min:1|max:31',
            'declaration_to_day' => 'nullable|integer|min:1|max:31',
            'declaration_year_from' => 'nullable|date',
            'declaration_year_to' => 'nullable|date',
            'proof_mandatory' => 'nullable|in:0,1',
            'payroll_tds_enabled' => 'nullable|in:0,1',
            'income_tax_plan_old_id' => 'nullable|integer|min:0',
            'income_tax_plan_new_id' => 'nullable|integer|min:0',
            // Payout
            'payout_mode' => 'nullable|in:bank_transfer,cash',
            'payout_requires_approval' => 'nullable|in:0,1',
            // Payslip
            'show_employer_contrib' => 'nullable|in:0,1',
            'show_ytd_totals' => 'nullable|in:0,1',
            'show_leave_balance' => 'nullable|in:0,1',
            'payroll_pf_enabled' => 'nullable|in:0,1',
            'pf_excluded_in_ctc' => 'nullable|in:0,1',
            'payroll_pf_rate' => 'nullable|numeric|min:0|max:100',
            'payroll_pf_cap' => 'nullable|numeric|min:0',
            'payroll_esi_enabled' => 'nullable|in:0,1',
            'esi_excluded_in_ctc' => 'nullable|in:0,1',
            'payroll_esi_rate' => 'nullable|numeric|min:0|max:100',
            'payroll_esi_cap' => 'nullable|numeric|min:0',
            'payroll_pt_enabled' => 'nullable|in:0,1',
            'payroll_pt_amount' => 'nullable|numeric|min:0',
            'redirect_to' => 'nullable|string',
        ]);
        // Payroll policy
        if ($request->has('payroll_currency')) {
            $this->setPayrollSetting('payroll_currency', $request->input('payroll_currency', 'INR'));
        }
        if ($request->has('payroll_pay_day')) {
            $this->setPayrollSetting('payroll_pay_day', (string) $request->input('payroll_pay_day', 'last_day'));
        }
        if ($request->has('payroll_rounding_mode')) {
            $this->setPayrollSetting('payroll_rounding_mode', (string) $request->input('payroll_rounding_mode', 'nearest_rupee'));
        }
        if ($request->has('payroll_lop_basis')) {
            $this->setPayrollSetting('payroll_lop_basis', (string) $request->input('payroll_lop_basis', 'gross'));
        }
        if ($request->has('payroll_working_days_source')) {
            $this->setPayrollSetting('payroll_working_days_source', (string) $request->input('payroll_working_days_source', 'weekday_minus_holidays'));
        }
        // Declaration
        if ($request->has('payroll_declaration_window_enabled')) {
            $this->setPayrollSetting('payroll_declaration_window_enabled', $request->input('payroll_declaration_window_enabled', '0'));
        }
        if ($request->has('declaration_from_day')) {
            $this->setPayrollSetting('declaration_from_day', (string) $request->input('declaration_from_day', 1));
        }
        if ($request->has('declaration_to_day')) {
            $this->setPayrollSetting('declaration_to_day', (string) $request->input('declaration_to_day', 25));
        }
        if ($request->has('declaration_year_from')) {
            $this->setPayrollSetting('declaration_year_from', (string) $request->input('declaration_year_from', ''));
        }
        if ($request->has('declaration_year_to')) {
            $this->setPayrollSetting('declaration_year_to', (string) $request->input('declaration_year_to', ''));
        }
        if ($request->has('proof_mandatory')) {
            $this->setPayrollSetting('proof_mandatory', (string) $request->input('proof_mandatory', '0'));
        }
        if ($request->has('payroll_tds_enabled')) {
            $this->setPayrollSetting('payroll_tds_enabled', (string) $request->input('payroll_tds_enabled', '0'));
        }
        if ($request->has('income_tax_plan_old_id')) {
            $this->setPayrollSetting('income_tax_plan_old_id', (string) $request->input('income_tax_plan_old_id', ''));
        }
        if ($request->has('income_tax_plan_new_id')) {
            $this->setPayrollSetting('income_tax_plan_new_id', (string) $request->input('income_tax_plan_new_id', ''));
        }
        // Payout
        if ($request->has('payout_mode')) {
            $this->setPayrollSetting('payout_mode', (string) $request->input('payout_mode', 'bank_transfer'));
        }
        if ($request->has('payout_requires_approval')) {
            $this->setPayrollSetting('payout_requires_approval', (string) $request->input('payout_requires_approval', '0'));
        }
        // Payslip
        if ($request->has('show_employer_contrib')) {
            $this->setPayrollSetting('show_employer_contrib', (string) $request->input('show_employer_contrib', '0'));
        }
        if ($request->has('show_ytd_totals')) {
            $this->setPayrollSetting('show_ytd_totals', (string) $request->input('show_ytd_totals', '0'));
        }
        if ($request->has('show_leave_balance')) {
            $this->setPayrollSetting('show_leave_balance', (string) $request->input('show_leave_balance', '0'));
        }
        $this->setPayrollSetting('payroll_pf_enabled', $request->input('payroll_pf_enabled', '0'));
        $this->setPayrollSetting('pf_excluded_in_ctc', $request->input('pf_excluded_in_ctc', '0'));
        $this->setPayrollSetting('payroll_pf_rate', $request->input('payroll_pf_rate', '12'));
        $this->setPayrollSetting('payroll_pf_cap', $request->input('payroll_pf_cap', '15000'));
        $this->setPayrollSetting('payroll_esi_enabled', $request->input('payroll_esi_enabled', '0'));
        $this->setPayrollSetting('esi_excluded_in_ctc', $request->input('esi_excluded_in_ctc', '0'));
        $this->setPayrollSetting('payroll_esi_rate', $request->input('payroll_esi_rate', '0.75'));
        $this->setPayrollSetting('payroll_esi_cap', $request->input('payroll_esi_cap', '21000'));
        $this->setPayrollSetting('payroll_pt_enabled', $request->input('payroll_pt_enabled', '0'));
        $this->setPayrollSetting('payroll_pt_amount', $request->input('payroll_pt_amount', '200'));
        $redirectTo = $request->input('redirect_to');
        return redirect($redirectTo ?: route('payroll.index', ['tab' => 'setup', 'sub' => 'pf_esi']))->with('success', 'Payroll settings saved.');
    }

    public function generateIndex()
    {
        $companyId = $this->companyId();
        $year = (int) request()->input('year', date('Y'));
        $month = (int) request()->input('month', date('n'));
        $year = max(2020, min(2030, $year));
        $month = max(1, min(12, $month));
        $selectedPeriod = sprintf('%04d-%02d', $year, $month);
        $currentPeriod = now()->format('Y-m');
        if ($selectedPeriod > $currentPeriod) {
            $year = (int) now()->format('Y');
            $month = (int) now()->format('n');
        }
        $stats = $this->salaryGroupCoverageStats($companyId);
        return view('payroll.generate', compact('year', 'month', 'stats'));
    }

    public function storeGenerate(Request $request, PayrollService $payrollService)
    {
        $validated = $request->validate([
            'year' => 'required|integer|min:2020|max:2030',
            'month' => 'required|integer|min:1|max:12',
            'async' => 'nullable|in:0,1',
        ]);
        $companyId = $this->companyId();
        if (! $companyId) {
            return redirect()->route('payroll.generate')->with('error', 'Company not found.');
        }
        $year = (int) $validated['year'];
        $month = (int) $validated['month'];
        $selectedPeriod = sprintf('%04d-%02d', $year, $month);
        $currentPeriod = now()->format('Y-m');
        if ($selectedPeriod > $currentPeriod) {
            return redirect()->route('payroll.generate', ['year' => now()->year, 'month' => now()->month])
                ->withErrors(['month' => 'Future payroll periods are not allowed.'])
                ->withInput();
        }

        $stats = $this->salaryGroupCoverageStats($companyId);
        if (($stats['total_groups'] ?? 0) === 0) {
            return redirect()->route('salary-groups.index')
                ->with('error', 'No salary groups found. Please create a salary group before running payroll.');
        }

        if (($validated['async'] ?? '0') === '1') {
            GeneratePayrollJob::dispatch($companyId, $year, $month);
            return redirect()->route('payroll.index', ['year' => $year, 'month' => $month])
                ->with('success', 'Payroll generation started in the background. Refresh the list in a moment.');
        }

        $result = $payrollService->generate($companyId, $year, $month);
        $msg = $result['generated'] . ' payroll record(s) generated.';
        if ($result['skipped'] > 0) {
            $msg .= ' ' . $result['skipped'] . ' employee(s) skipped (no salary history or salary group structure found).';
        }
        if (($stats['without_salary_group'] ?? 0) > 0) {
            $msg .= ' ' . $stats['without_salary_group'] . ' employee(s) have no salary group assignment.';
        }
        return redirect()->route('payroll.index', ['year' => $year, 'month' => $month])->with('success', $msg);
    }

    /**
     * @return array{total_staff:int,total_groups:int,with_salary_group:int,without_salary_group:int}
     */
    private function salaryGroupCoverageStats(?int $companyId): array
    {
        if (! $companyId) {
            return [
                'total_staff' => 0,
                'total_groups' => 0,
                'with_salary_group' => 0,
                'without_salary_group' => 0,
            ];
        }

        $staffQuery = User::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->forActiveDirectory();

        $totalStaff = (clone $staffQuery)->count();
        $withSalaryGroup = (clone $staffQuery)->whereNotNull('salary_group_id')->count();

        return [
            'total_staff' => $totalStaff,
            'total_groups' => SalaryGroup::withoutGlobalScopes()->where('company_id', $companyId)->count(),
            'with_salary_group' => $withSalaryGroup,
            'without_salary_group' => max(0, $totalStaff - $withSalaryGroup),
        ];
    }

    public function payslipsIndex(Request $request)
    {
        $query = Payroll::with('user');
        $companyId = auth()->user()->company_id;
        if ($companyId) {
            $query->where('company_id', $companyId);
        }
        if ($request->filled('year')) {
            $query->where('year', $request->year);
        }
        if ($request->filled('month')) {
            $query->where('month', $request->month);
        }
        $payrolls = $query->orderByDesc('year')->orderByDesc('month')->paginate(20)->withQueryString();
        return view('payroll.payslips', compact('payrolls'));
    }

    public function showPayslip(Payroll $payroll)
    {
        $companyId = $this->companyId();
        if ($companyId && $payroll->company_id !== $companyId) {
            abort(403);
        }
        $payroll->load(['user.company', 'payrollComponents']);
        return view('payroll.payslip-show', compact('payroll'));
    }

    public function markPaid(Request $request, Payroll $payroll)
    {
        $companyId = $this->companyId();
        if ($companyId && $payroll->company_id !== $companyId) {
            abort(403);
        }
        $payroll->update(['status' => 'paid', 'payment_date' => now()]);
        return redirect()->back()->with('success', 'Marked as paid.');
    }

    public function downloadPayslip(Payroll $payroll)
    {
        $companyId = $this->companyId();
        if ($companyId && $payroll->company_id !== $companyId) {
            abort(403);
        }
        if (! class_exists(\Barryvdh\DomPDF\Facade\Pdf::class)) {
            return redirect()->back()->with('error', 'PDF download requires barryvdh/laravel-dompdf. Run: composer require barryvdh/laravel-dompdf');
        }
        $payroll->load(['user.company', 'payrollComponents']);
        $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('payroll.payslip-pdf', compact('payroll'));
        return $pdf->download('payslip-' . ($payroll->user_id ?? 0) . '-' . $payroll->year . '-' . str_pad((string) $payroll->month, 2, '0', STR_PAD_LEFT) . '.pdf');
    }

    public function summaryReport(Request $request)
    {
        $companyId = $this->companyId();
        $year = $request->filled('year') ? (int) $request->year : (int) date('Y');
        $month = $request->filled('month') ? (int) $request->month : null;

        $query = Payroll::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('year', $year);
        if ($month !== null) {
            $query->where('month', $month);
        }
        $payrolls = $query->get();

        $totalGross = $payrolls->sum('salary_amount');
        $totalNet = $payrolls->sum('net_salary');
        $totalDeductions = $totalGross - $totalNet;
        $headcount = $payrolls->count();

        $byMonth = $payrolls->groupBy('month')->map(function ($items, $m) {
            $gross = $items->sum('salary_amount');
            $net = $items->sum('net_salary');
            return [
                'month' => (int) $m,
                'month_name' => date('F', mktime(0, 0, 0, (int) $m, 1)),
                'headcount' => $items->count(),
                'gross' => $gross,
                'net' => $net,
                'deductions' => $gross - $net,
            ];
        })->sortKeysDesc();

        return view('payroll.summary', [
            'year' => $year,
            'month' => $month,
            'totalGross' => $totalGross,
            'totalNet' => $totalNet,
            'totalDeductions' => $totalDeductions,
            'headcount' => $headcount,
            'byMonth' => $byMonth,
        ]);
    }
}
