<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\FinancialAccount;
use App\Models\Scopes\BranchScope;
use App\Rules\SameBranchAs;
use App\Services\AuditService;
use App\Services\BranchAccessService;
use App\Services\ExpenseService;
use App\Services\SecureFileService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Throwable;

class ExpenseController extends Controller
{
    public function __construct(private BranchAccessService $branchAccess)
    {
    }

    public function index(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $search = trim((string) $request->string('search'));
        $status = (string) $request->string('status');
        $categoryId = $request->integer('category_id');
        $from = (string) $request->string('from');
        $to = (string) $request->string('to');

        $query = Expense::query()->with(['branch','category','financialAccount','enteredBy','approver','rejecter'])
            ->whereIn('branch_id', $branchIds)
            ->when($search !== '', fn ($q) => $q->where(function ($sub) use ($search): void {
                $sub->where('expense_number','like',"%{$search}%")
                    ->orWhere('description','like',"%{$search}%")
                    ->orWhere('supplier','like',"%{$search}%");
            }))
            ->when(in_array($status, ['pending','approved','rejected'], true), fn ($q) => $q->where('status',$status))
            ->when($categoryId > 0, fn ($q) => $q->where('expense_category_id',$categoryId))
            ->when($from !== '', fn ($q) => $q->whereDate('expense_date','>=',$from))
            ->when($to !== '', fn ($q) => $q->whereDate('expense_date','<=',$to));

        $summaryBase = clone $query;
        $summary = [
            'pending' => (clone $summaryBase)->where('status','pending')->sum('amount'),
            'approved' => (clone $summaryBase)->where('status','approved')->sum('amount'),
            'rejected' => (clone $summaryBase)->where('status','rejected')->sum('amount'),
        ];

        $expenses = $query->latest('expense_date')->latest('id')->paginate(20)->withQueryString();
        $businessIds = Branch::whereIn('id',$branchIds)->pluck('business_id')->unique();
        $categories = ExpenseCategory::whereIn('business_id',$businessIds)->orderBy('name')->get();

        return view('expenses.index', compact('expenses','categories','summary'));
    }

    public function create(Request $request): View
    {
        [$branches,$categories,$accounts] = $this->formData($request);
        return view('expenses.create', compact('branches','categories','accounts'));
    }

    public function store(Request $request, ExpenseService $service, SecureFileService $files): RedirectResponse
    {
        $data = $request->validate([
            'branch_id' => ['required','integer','exists:branches,id'],
            'expense_category_id' => ['required','integer','exists:expense_categories,id'],
            'financial_account_id' => ['required','integer','exists:financial_accounts,id'],
            'description' => ['required','string','max:255'],
            'amount' => ['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'expense_date' => ['required','date'],
            'supplier' => ['nullable','string','max:180'],
            'receipt' => ['nullable','file','mimes:jpg,jpeg,png,webp,pdf','max:8192'],
            'notes' => ['nullable','string','max:3000'],
        ]);
        $this->assertBranchAccess($request, (int) $data['branch_id']);
        $account=FinancialAccount::withoutGlobalScope(BranchScope::class)->findOrFail($data['financial_account_id']);
        $this->assertBranchAccess($request,$account->branch_id);
        $request->validate([
            'financial_account_id'=>[
                new SameBranchAs('financial_accounts',(int)$data['branch_id'],'The paid-from account must belong to the selected branch.'),
            ],
        ]);

        $storedPath = null;
        try {
            $expense = DB::transaction(function () use ($request,$service,$files,$data,&$storedPath): Expense {
                $expense = $service->createPending($data, $request->user());
                if ($request->hasFile('receipt')) {
                    $file = $request->file('receipt');
                    $storedPath = $files->store($file, 'expenses/'.$expense->id);
                    $expense->update([
                        'receipt_original_name'=>$file->getClientOriginalName(),
                        'receipt_path'=>$storedPath,
                        'receipt_mime_type'=>$file->getMimeType(),
                        'receipt_size'=>$file->getSize(),
                    ]);
                }
                return $expense;
            });
        } catch (Throwable $exception) {
            $files->delete($storedPath);
            throw $exception;
        }

        AuditService::record('expense.created',$expense,[],[
            'expense_number'=>$expense->expense_number,'amount'=>(string)$expense->amount,'status'=>$expense->status,
            'expense_category_id'=>$expense->expense_category_id,'financial_account_id'=>$expense->financial_account_id,
        ],$expense->branch_id);

        return redirect()->route('expenses.show',$expense)->with('success','Expense submitted for approval. No account balance has been changed yet.');
    }

    public function show(Request $request, Expense $expense): View
    {
        $this->assertBranchAccess($request,$expense->branch_id);
        $expense->load(['branch.business','category','financialAccount','enteredBy','approver','rejecter','accountTransaction','recurringExpense']);
        return view('expenses.show', compact('expense'));
    }

    public function decision(Request $request, Expense $expense, ExpenseService $service): RedirectResponse
    {
        $this->assertBranchAccess($request,$expense->branch_id);
        $data = $request->validate([
            'decision'=>['required',Rule::in(['approve','reject'])],
            'comments'=>['nullable','required_if:decision,reject','string','max:2000'],
        ]);
        $before = $expense->only(['status','approved_by','approved_at','rejected_by','rejected_at','decision_comments']);
        $updated = $data['decision'] === 'approve'
            ? $service->approve($expense,$request->user(),$data['comments'] ?? null)
            : $service->reject($expense,$request->user(),$data['comments']);

        AuditService::record('expense.'.$updated->status,$updated,$before,$updated->only([
            'status','approved_by','approved_at','rejected_by','rejected_at','decision_comments','financial_account_transaction_id','accounting_journal_number',
        ]),$updated->branch_id);

        return back()->with('success',$updated->status === 'approved'
            ? 'Expense approved and posted to the selected account and accounting ledger.'
            : 'Expense rejected. No financial account or ledger posting was created.');
    }

    public function receipt(Request $request, Expense $expense, SecureFileService $files): StreamedResponse
    {
        $this->assertBranchAccess($request,$expense->branch_id);
        abort_unless($expense->receipt_path,404);
        return $files->download($expense->receipt_path,$expense->receipt_original_name);
    }

    private function formData(Request $request): array
    {
        $branchIds = $this->allowedBranchIds($request);
        $branches = Branch::whereIn('id',$branchIds)->where('status','active')->orderBy('name')->get();
        $businessIds = $branches->pluck('business_id')->unique();
        $categories = ExpenseCategory::whereIn('business_id',$businessIds)->where('status','active')->orderBy('name')->get();
        $accounts = FinancialAccount::whereIn('branch_id',$branchIds)->where('status','active')->orderBy('name')->get();
        return [$branches,$categories,$accounts];
    }

    /** @return array<int> */
    private function allowedBranchIds(Request $request): array
    {
        return $this->branchAccess->accessibleBranchIds($request->user());
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        $this->branchAccess->assertBranchAccessible($request->user(), $branchId);
    }
}
