<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\EmployeeCompensation;
use App\Models\FinancialAccount;
use App\Models\PayrollEntry;
use App\Models\PayrollPeriod;
use App\Models\User;
use App\Rules\SameBranchAs;
use App\Services\AuditService;
use App\Services\PayrollPayslipPdfService;
use App\Services\PayrollService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\Rule;
use Illuminate\View\View;

class PayrollController extends Controller
{
    public function index(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $status = (string) $request->string('status');
        $branchId = $request->integer('branch_id');
        $search = trim((string) $request->string('search'));

        $query = PayrollPeriod::query()
            ->with(['branch', 'creator', 'approver', 'paidFromAccount', 'payer'])
            ->withCount('entries')
            ->whereIn('branch_id', $branchIds)
            ->when($branchId > 0, fn ($q) => $q->where('branch_id', $branchId))
            ->when(in_array($status, PayrollPeriod::STATUSES, true), fn ($q) => $q->where('status', $status))
            ->when($search !== '', fn ($q) => $q->where(function ($sub) use ($search): void {
                $sub->where('period_number', 'like', "%{$search}%")->orWhere('name', 'like', "%{$search}%");
            }));

        $summaryBase = clone $query;
        $summary = [
            'draft' => (clone $summaryBase)->where('status', 'draft')->sum('total_net'),
            'approved' => (clone $summaryBase)->where('status', 'approved')->sum('total_net'),
            'paid' => (clone $summaryBase)->where('status', 'paid')->sum('total_net'),
        ];
        $periods = $query->latest('start_date')->latest('id')->paginate(20)->withQueryString();
        $branches = Branch::whereIn('id', $branchIds)->orderBy('name')->get();

        return view('payroll.index', compact('periods', 'branches', 'summary'));
    }

    public function compensations(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $employees = User::query()
            ->with('primaryBranch')
            ->whereIn('primary_branch_id', $branchIds)
            ->orderBy('name')
            ->get();
        $compensations = EmployeeCompensation::whereIn('user_id', $employees->pluck('id'))->get()->keyBy('user_id');
        foreach ($employees as $employee) $employee->setRelation('compensation', $compensations->get($employee->id));

        return view('payroll.compensations', compact('employees'));
    }

    public function updateCompensation(Request $request, User $employee, PayrollService $service): RedirectResponse
    {
        abort_unless($employee->primary_branch_id && $request->user()->canAccessBranch($employee->primary_branch_id), 403);
        $data = $request->validate($this->componentRules() + [
            'effective_from' => ['nullable', 'date'],
            'status' => ['required', Rule::in(['active', 'inactive'])],
            'notes' => ['nullable', 'string', 'max:2000'],
        ]);
        $before = EmployeeCompensation::where('user_id', $employee->id)->first()?->toArray() ?? [];
        $compensation = $service->configureCompensation($employee, $data, $request->user());
        AuditService::record('payroll.compensation.updated', $compensation, $before, $compensation->fresh()->toArray(), $employee->primary_branch_id);
        return back()->with('success', 'Compensation settings updated for '.$employee->name.'.');
    }

    public function create(Request $request): View
    {
        $branches = Branch::whereIn('id', $this->allowedBranchIds($request))->where('status', 'active')->orderBy('name')->get();
        return view('payroll.create', compact('branches'));
    }

    public function store(Request $request, PayrollService $service): RedirectResponse
    {
        $data = $request->validate([
            'branch_id' => ['required', 'integer', 'exists:branches,id'],
            'name' => ['required', 'string', 'max:160'],
            'start_date' => ['required', 'date'],
            'end_date' => ['required', 'date', 'after_or_equal:start_date'],
            'pay_date' => ['required', 'date', 'after_or_equal:end_date'],
        ]);
        $this->assertBranchAccess($request, (int) $data['branch_id']);
        $period = $service->createPeriod($data, $request->user());
        AuditService::record('payroll.period.created', $period, [], [
            'period_number' => $period->period_number, 'start_date' => $period->start_date, 'end_date' => $period->end_date,
            'total_gross' => $period->total_gross, 'total_net' => $period->total_net, 'status' => $period->status,
        ], $period->branch_id);
        return redirect()->route('payroll.show', $period)->with('success', 'Draft payroll period created from current employee compensation settings.');
    }

    public function show(Request $request, PayrollPeriod $period): View
    {
        $this->assertBranchAccess($request, $period->branch_id);
        $period->load(['branch.business', 'entries.employee', 'creator', 'approver', 'rejecter', 'paidFromAccount', 'payer']);
        $accounts = FinancialAccount::where('branch_id', $period->branch_id)->where('status', 'active')->orderBy('name')->get();
        return view('payroll.show', compact('period', 'accounts'));
    }

    public function updateEntry(Request $request, PayrollPeriod $period, PayrollEntry $entry, PayrollService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $period->branch_id);
        abort_unless($entry->payroll_period_id === $period->id, 404);
        $data = $request->validate($this->componentRules());
        $before = $entry->only(array_merge(array_keys($this->componentRules()), ['gross', 'total_deductions', 'net']));
        $updated = $service->updateEntry($entry, $data, $request->user());
        AuditService::record('payroll.entry.updated', $updated, $before, $updated->only([
            'basic_salary', 'allowances', 'bonus', 'commission', 'overtime', 'deductions', 'advances', 'other_deductions', 'gross', 'total_deductions', 'net',
        ]), $period->branch_id);
        return back()->with('success', 'Payroll components updated for '.$entry->employee_name_snapshot.'.');
    }

    public function decision(Request $request, PayrollPeriod $period, PayrollService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $period->branch_id);
        $data = $request->validate([
            'decision' => ['required', Rule::in(['approve', 'reject'])],
            'comments' => ['nullable', 'required_if:decision,reject', 'string', 'max:2000'],
        ]);
        $before = $period->only(['status', 'approved_by', 'approved_at', 'rejected_by', 'rejected_at', 'decision_comments']);
        $updated = $data['decision'] === 'approve'
            ? $service->approve($period, $request->user(), $data['comments'] ?? null)
            : $service->reject($period, $request->user(), $data['comments']);
        AuditService::record('payroll.period.'.$updated->status, $updated, $before, $updated->only([
            'status', 'approved_by', 'approved_at', 'rejected_by', 'rejected_at', 'decision_comments',
        ]), $updated->branch_id);
        return back()->with('success', $updated->status === 'approved' ? 'Payroll approved and locked for payment.' : 'Payroll rejected without financial posting.');
    }

    public function pay(Request $request, PayrollPeriod $period, PayrollService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $period->branch_id);
        $data = $request->validate([
            'financial_account_id' => [
                'required', 'integer', 'exists:financial_accounts,id',
                new SameBranchAs('financial_accounts', (int) $period->branch_id, 'The payroll payment account must belong to the payroll branch.'),
            ],
        ]);
        $before = $period->only(['status', 'paid_from_account_id', 'paid_by', 'paid_at']);
        $updated = $service->pay($period, FinancialAccount::findOrFail($data['financial_account_id']), $request->user());
        AuditService::record('payroll.period.paid', $updated, $before, $updated->only([
            'status', 'paid_from_account_id', 'paid_by', 'paid_at', 'total_net',
        ]), $updated->branch_id);
        return back()->with('success', 'Payroll paid in full. Account transactions, payroll ledger entries and payslips are now permanent.');
    }

    public function payslip(Request $request, PayrollPeriod $period, PayrollEntry $entry): View
    {
        $this->assertPayslipAccess($request, $period, $entry);
        $entry->load(['period.branch.business', 'financialAccount', 'accountTransaction', 'payer']);
        return view('payroll.payslip', compact('entry'));
    }

    public function payslipPdf(Request $request, PayrollPeriod $period, PayrollEntry $entry, PayrollPayslipPdfService $pdf): Response
    {
        $this->assertPayslipAccess($request, $period, $entry);
        $entry->load(['period.branch.business', 'financialAccount', 'accountTransaction', 'payer']);
        $content = $pdf->generate($entry);
        return response($content, 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'attachment; filename="'.$entry->payslip_number.'.pdf"',
            'Content-Length' => (string) strlen($content),
        ]);
    }

    private function componentRules(): array
    {
        return [
            'basic_salary' => ['required', 'decimal:0,2', 'min:0'],
            'allowances' => ['required', 'decimal:0,2', 'min:0'],
            'bonus' => ['required', 'decimal:0,2', 'min:0'],
            'commission' => ['required', 'decimal:0,2', 'min:0'],
            'overtime' => ['required', 'decimal:0,2', 'min:0'],
            'deductions' => ['required', 'decimal:0,2', 'min:0'],
            'advances' => ['required', 'decimal:0,2', 'min:0'],
            'other_deductions' => ['required', 'decimal:0,2', 'min:0'],
        ];
    }

    private function allowedBranchIds(Request $request)
    {
        return collect(app(\App\Services\BranchAccessService::class)->accessibleBranchIds($request->user()));
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        abort_unless($request->user()->canAccessBranch($branchId), 403);
    }

    private function assertPayslipAccess(Request $request, PayrollPeriod $period, PayrollEntry $entry): void
    {
        $this->assertBranchAccess($request, $period->branch_id);
        abort_unless($entry->payroll_period_id === $period->id, 404);
        abort_unless($entry->status === 'paid' && $entry->paid_at !== null, 422, 'Payslip is available after payroll payment.');
    }
}
