<?php

namespace App\Http\Controllers;

use App\Models\Account;
use App\Models\JournalEntry;
use App\Services\AccountingService;
use App\Support\BranchContext;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\View\View;

class JournalEntryController extends Controller implements HasMiddleware
{
    public function __construct(private AccountingService $accounting)
    {
    }

    public static function middleware(): array
    {
        return [
            new Middleware('can:accounting.view', only: ['index', 'show']),
            new Middleware('can:accounting.create', only: ['create', 'store', 'income', 'storeIncome', 'expense', 'storeExpense']),
            new Middleware('can:accounting.delete', only: ['destroy']),
        ];
    }

    public function index(Request $request): View
    {
        $type = $request->input('type');
        $entries = JournalEntry::with('lines')
            ->when($type, fn ($q) => $q->where('type', $type))
            ->latest('date')->latest('id')->paginate(20)->withQueryString();

        return view('accounting.journals.index', compact('entries', 'type'));
    }

    public function create(): View
    {
        return view('accounting.journals.create', ['accounts' => $this->accounts()]);
    }

    public function store(Request $request, BranchContext $context): RedirectResponse
    {
        $data = $request->validate([
            'date' => ['required', 'date'],
            'description' => ['required', 'string', 'max:255'],
            'reference' => ['nullable', 'string', 'max:100'],
            'lines' => ['required', 'array', 'min:2'],
            'lines.*.account_id' => ['nullable', 'exists:accounts,id'],
            'lines.*.debit' => ['nullable', 'numeric', 'min:0'],
            'lines.*.credit' => ['nullable', 'numeric', 'min:0'],
        ]);

        $lines = collect($data['lines'])->filter(fn ($l) => ! empty($l['account_id']))->values()->all();
        $branchId = $this->resolveBranch((int) ($lines[0]['account_id'] ?? 0), $context);

        $entry = $this->accounting->postEntry($branchId, $data['date'], $data['description'], $lines, 'journal', ['reference' => $data['reference'] ?? null]);

        return redirect()->route('journals.show', $entry)->with('success', 'Journal entry posted.');
    }

    public function income(): View
    {
        return view('accounting.journals.income', [
            'incomeAccounts' => $this->byTypes(['income']),
            'depositAccounts' => $this->cashBank(),
        ]);
    }

    public function storeIncome(Request $request, BranchContext $context): RedirectResponse
    {
        $data = $request->validate([
            'date' => ['required', 'date'],
            'income_account_id' => ['required', 'exists:accounts,id'],
            'deposit_account_id' => ['required', 'exists:accounts,id'],
            'amount' => ['required', 'numeric', 'min:0.01'],
            'description' => ['required', 'string', 'max:255'],
            'reference' => ['nullable', 'string', 'max:100'],
        ]);
        $branchId = $this->resolveBranch((int) $data['income_account_id'], $context);

        $entry = $this->accounting->recordIncome($branchId, $data['income_account_id'], $data['deposit_account_id'], $data['amount'], $data['date'], $data['description'], $data['reference'] ?? null);

        return redirect()->route('journals.show', $entry)->with('success', 'Income recorded.');
    }

    public function expense(): View
    {
        return view('accounting.journals.expense', [
            'expenseAccounts' => $this->byTypes(['expense']),
            'payAccounts' => $this->cashBank(),
        ]);
    }

    public function storeExpense(Request $request, BranchContext $context): RedirectResponse
    {
        $data = $request->validate([
            'date' => ['required', 'date'],
            'pay_account_id' => ['required', 'exists:accounts,id'],
            'expenses' => ['required', 'array', 'min:1'],
            'expenses.*.expense_account_id' => ['required', 'exists:accounts,id'],
            'expenses.*.amount' => ['required', 'numeric', 'min:0.01'],
            'expenses.*.description' => ['required', 'string', 'max:255'],
            'expenses.*.reference' => ['nullable', 'string', 'max:100'],
        ]);

        $last = null;
        $count = 0;
        foreach ($data['expenses'] as $row) {
            $branchId = $this->resolveBranch((int) $row['expense_account_id'], $context);
            $last = $this->accounting->recordExpense(
                $branchId, $row['expense_account_id'], $data['pay_account_id'],
                $row['amount'], $data['date'], $row['description'], $row['reference'] ?? null
            );
            $count++;
        }

        if ($count === 1) {
            return redirect()->route('journals.show', $last)->with('success', 'Expense recorded.');
        }

        return redirect()->route('journals.index')->with('success', $count.' expenses recorded.');
    }

    public function show(JournalEntry $journal): View
    {
        $journal->load('lines.account');

        return view('accounting.journals.show', ['entry' => $journal]);
    }

    public function destroy(JournalEntry $journal): RedirectResponse
    {
        if ($journal->source_type) {
            return back()->with('error', 'Auto-generated entries cannot be deleted here. Remove the source transaction instead.');
        }
        $journal->delete();

        return redirect()->route('journals.index')->with('success', 'Journal entry deleted.');
    }

    /** Branch of the chosen account (so Super Admins on "All Branches" still post correctly). */
    private function resolveBranch(int $accountId, BranchContext $context): int
    {
        $branchId = optional(Account::find($accountId))->branch_id
            ?? $context->currentId()
            ?? auth()->user()->branch_id;

        abort_if(! $branchId, 422, 'Could not resolve a branch for this entry. Pick a branch first.');

        return (int) $branchId;
    }

    private function accounts()
    {
        return Account::active()->orderBy('code')->get();
    }

    private function byTypes(array $types)
    {
        return Account::active()->whereIn('type', $types)->orderBy('code')->get();
    }

    private function cashBank()
    {
        return Account::active()->whereNotNull('cash_bank')->orderBy('code')->get();
    }
}
