<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\CashReconciliation;
use App\Models\DailyClosing;
use App\Models\FinancialAccount;
use App\Services\AuditService;
use App\Services\DailyClosingService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class CashClosingController extends Controller
{
    public function index(Request $request, DailyClosingService $service): View
    {
        $branchIds = $this->allowedBranchIds($request);
        abort_if($branchIds->isEmpty(), 403, 'No branch access is assigned to this user.');

        $data = validator([
            'branch_id' => $request->input('branch_id', $branchIds->first()),
            'date' => $request->input('date', now()->toDateString()),
        ], [
            'branch_id' => ['required','integer','exists:branches,id'],
            'date' => ['required','date'],
        ])->validate();

        $branch = Branch::findOrFail((int) $data['branch_id']);
        $this->assertBranchAccess($request, $branch->id);
        $preview = $service->preview($branch, (string) $data['date']);
        $branches = Branch::whereIn('id', $branchIds)->orderBy('name')->get();
        $history = DailyClosing::query()
            ->with(['branch','preparer','managerConfirmer'])
            ->whereIn('branch_id', $branchIds)
            ->latest('closing_date')
            ->latest('id')
            ->paginate(15)
            ->withQueryString();

        return view('cash-closing.index', compact('branches','branch','preview','history'));
    }

    public function reconcile(Request $request, DailyClosingService $service): RedirectResponse
    {
        $data = $request->validate([
            'branch_id' => ['required','integer','exists:branches,id'],
            'financial_account_id' => ['required','integer','exists:financial_accounts,id'],
            'date' => ['required','date'],
            'actual_balance' => ['nullable','required_without:physical_cash','regex:/^\d+(?:\.\d{1,2})?$/'],
            // Retain the former field name for existing clients/tests while the UI moves to generic account balances.
            'physical_cash' => ['nullable','required_without:actual_balance','regex:/^\d+(?:\.\d{1,2})?$/'],
            'variance_explanation' => ['nullable','string','max:3000'],
        ]);
        $branch = Branch::findOrFail((int) $data['branch_id']);
        $this->assertBranchAccess($request, $branch->id);
        $account = FinancialAccount::findOrFail((int) $data['financial_account_id']);
        $previous = CashReconciliation::query()
            ->where('financial_account_id', $account->id)
            ->whereDate('reconciliation_date', $data['date'])
            ->where('status', 'active')
            ->first();
        $actualBalance = (string) ($data['actual_balance'] ?? $data['physical_cash']);

        $reconciliation = $service->createReconciliation(
            $branch,
            $account,
            (string) $data['date'],
            $actualBalance,
            $data['variance_explanation'] ?? null,
            $request->user()
        );

        AuditService::record('cash_reconciliation.created', $reconciliation, $previous?->toArray() ?? [], [
            'reconciliation_number' => $reconciliation->reconciliation_number,
            'version' => $reconciliation->version,
            'financial_account_id' => $reconciliation->financial_account_id,
            'reconciliation_date' => $reconciliation->reconciliation_date,
            'expected_cash' => $reconciliation->expected_cash,
            'physical_cash' => $reconciliation->physical_cash,
            'variance' => $reconciliation->variance,
        ], $reconciliation->branch_id);

        return redirect()->route('cash-closing.index', ['branch_id'=>$branch->id,'date'=>$data['date']])
            ->with('success', $previous
                ? 'Account reconciliation saved as a new permanent version; the previous version was preserved.'
                : 'Account reconciled and recorded permanently.');
    }

    public function prepare(Request $request, DailyClosingService $service): RedirectResponse
    {
        $data = $request->validate([
            'branch_id' => ['required','integer','exists:branches,id'],
            'date' => ['required','date'],
        ]);
        $branch = Branch::findOrFail((int) $data['branch_id']);
        $this->assertBranchAccess($request, $branch->id);
        $before = DailyClosing::query()->where('branch_id',$branch->id)->whereDate('closing_date',$data['date'])->first();
        $closing = $service->prepareOrRefresh($branch, (string) $data['date'], $request->user());

        AuditService::record($before ? 'daily_closing.refreshed' : 'daily_closing.prepared', $closing, $before?->toArray() ?? [], [
            'closing_number' => $closing->closing_number,
            'closing_date' => $closing->closing_date,
            'opening_funds' => $closing->opening_funds,
            'collections' => $closing->collections,
            'disbursements' => $closing->disbursements,
            'expenses' => $closing->expenses,
            'closing_funds' => $closing->closing_funds,
            'expected_cash' => $closing->expected_cash,
            'actual_cash' => $closing->actual_cash,
            'variance' => $closing->variance,
            'status' => $closing->status,
        ], $closing->branch_id);

        return redirect()->route('cash-closing.show', $closing)
            ->with('success', $before ? 'Daily closing refreshed from the latest ledger and account reconciliations.' : 'Daily closing prepared for manager confirmation.');
    }

    public function show(Request $request, DailyClosing $dailyClosing, DailyClosingService $service): View
    {
        $this->assertBranchAccess($request, $dailyClosing->branch_id);
        $dailyClosing->load(['branch.business','preparer','managerConfirmer']);
        $preview = $service->preview($dailyClosing->branch, $dailyClosing->closing_date);
        $reconciliations = CashReconciliation::query()
            ->with(['financialAccount','reconciler','supersededBy'])
            ->where('branch_id', $dailyClosing->branch_id)
            ->whereDate('reconciliation_date', $dailyClosing->closing_date)
            ->orderBy('financial_account_id')
            ->orderByDesc('version')
            ->get();

        return view('cash-closing.show', compact('dailyClosing','preview','reconciliations'));
    }

    public function confirm(Request $request, DailyClosing $dailyClosing, DailyClosingService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $dailyClosing->branch_id);
        $data = $request->validate(['manager_comments' => ['nullable','string','max:3000']]);
        $before = $dailyClosing->toArray();
        $closing = $service->confirm($dailyClosing, $request->user(), $data['manager_comments'] ?? null);

        AuditService::record('daily_closing.confirmed', $closing, $before, [
            'status' => $closing->status,
            'manager_confirmed_by' => $closing->manager_confirmed_by,
            'manager_confirmed_at' => $closing->manager_confirmed_at,
            'manager_comments' => $closing->manager_comments,
            'snapshot_fingerprint' => $closing->snapshot_fingerprint,
        ], $closing->branch_id);

        return redirect()->route('cash-closing.show', $closing)
            ->with('success', 'Branch day manager-confirmed. The financial books are now permanently closed through this date.');
    }

    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);
    }
}
