<?php

namespace App\Http\Controllers;

use App\Models\Account;
use App\Models\JournalLine;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\Support\Carbon;
use Illuminate\View\View;

class AccountingReportController extends Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [new Middleware('can:accounting.view')];
    }

    /** General ledger for one account with a running balance. */
    public function ledger(Request $request): View
    {
        $accountId = $request->integer('account_id') ?: null;
        $from = $request->date('from') ?: Carbon::today()->startOfYear();
        $to = $request->date('to') ?: Carbon::today();

        $account = $accountId ? Account::find($accountId) : null;
        $rows = collect();
        $opening = 0.0;

        if ($account) {
            // Opening balance before the period.
            $opening = $account->signedBalance($from->copy()->subDay()->toDateString());
            $running = $opening;

            $rows = $account->lines()
                ->whereHas('entry', fn ($e) => $e->whereBetween('date', [$from, $to]))
                ->with('entry')->get()
                ->sortBy(fn ($l) => [$l->entry->date->timestamp, $l->id])
                ->map(function ($l) use (&$running) {
                    $running += (float) $l->debit - (float) $l->credit;

                    return [
                        'date' => $l->entry->date, 'entry' => $l->entry->entry_number,
                        'description' => $l->entry->description, 'debit' => (float) $l->debit,
                        'credit' => (float) $l->credit, 'balance' => round($running, 2),
                    ];
                })->values();
        }

        return view('accounting.reports.ledger', [
            'accounts' => Account::orderBy('code')->get(),
            'account' => $account, 'rows' => $rows, 'opening' => $opening,
            'from' => $from->toDateString(), 'to' => $to->toDateString(),
        ]);
    }

    public function cashBook(Request $request): View
    {
        return $this->cashBank($request, 'cash');
    }

    public function bankBook(Request $request): View
    {
        return $this->cashBank($request, 'bank');
    }

    /** Cash Book / Bank Book — ledgers of the cash/bank accounts. */
    public function cashBank(Request $request, string $kind): View
    {
        $from = $request->date('from') ?: Carbon::today()->startOfMonth();
        $to = $request->date('to') ?: Carbon::today();

        $accounts = Account::where('cash_bank', $kind)->orderBy('code')->get();
        $books = $accounts->map(function ($account) use ($from, $to) {
            $opening = $account->signedBalance($from->copy()->subDay()->toDateString());
            $running = $opening;
            $lines = $account->lines()
                ->whereHas('entry', fn ($e) => $e->whereBetween('date', [$from, $to]))
                ->with('entry')->get()
                ->sortBy(fn ($l) => [$l->entry->date->timestamp, $l->id])
                ->map(function ($l) use (&$running) {
                    $running += (float) $l->debit - (float) $l->credit;

                    return ['date' => $l->entry->date, 'entry' => $l->entry->entry_number, 'description' => $l->entry->description,
                        'debit' => (float) $l->debit, 'credit' => (float) $l->credit, 'balance' => round($running, 2)];
                })->values();

            return ['account' => $account, 'opening' => $opening, 'lines' => $lines, 'closing' => $running];
        });

        return view('accounting.reports.cashbank', [
            'kind' => $kind, 'books' => $books, 'from' => $from->toDateString(), 'to' => $to->toDateString(),
        ]);
    }

    /** Trial Balance as of a date. */
    public function trialBalance(Request $request): View
    {
        $asOf = $request->date('as_of') ?: Carbon::today();

        $rows = Account::orderBy('code')->get()->map(function ($a) use ($asOf) {
            $signed = $a->signedBalance($asOf->toDateString());

            return ['account' => $a, 'debit' => max($signed, 0), 'credit' => max(-$signed, 0)];
        })->filter(fn ($r) => $r['debit'] != 0 || $r['credit'] != 0)->values();

        return view('accounting.reports.trial-balance', [
            'rows' => $rows,
            'totalDebit' => $rows->sum('debit'),
            'totalCredit' => $rows->sum('credit'),
            'asOf' => $asOf->toDateString(),
        ]);
    }

    /** Profit & Loss for a period. */
    public function profitLoss(Request $request): View
    {
        $from = $request->date('from') ?: Carbon::today()->startOfYear();
        $to = $request->date('to') ?: Carbon::today();

        $income = $this->periodActivity('income', $from, $to);  // credit − debit
        $expense = $this->periodActivity('expense', $from, $to, true); // debit − credit

        return view('accounting.reports.profit-loss', [
            'income' => $income, 'expense' => $expense,
            'totalIncome' => $income->sum('amount'), 'totalExpense' => $expense->sum('amount'),
            'from' => $from->toDateString(), 'to' => $to->toDateString(),
        ]);
    }

    /** Balance Sheet as of a date. */
    public function balanceSheet(Request $request): View
    {
        $asOf = $request->date('as_of') ?: Carbon::today();
        $d = $asOf->toDateString();

        $group = fn (string $type) => Account::type($type)->orderBy('code')->get()
            ->map(fn ($a) => ['account' => $a, 'amount' => $a->naturalBalance($d)])
            ->filter(fn ($r) => abs($r['amount']) > 0.001)->values();

        $assets = $group('asset');
        $liabilities = $group('liability');
        $equity = $group('equity');

        // Net profit (income − expense to date) rolls into equity.
        $netProfit = $this->periodActivity('income', Carbon::create(2000, 1, 1), $asOf)->sum('amount')
            - $this->periodActivity('expense', Carbon::create(2000, 1, 1), $asOf, true)->sum('amount');

        return view('accounting.reports.balance-sheet', [
            'assets' => $assets, 'liabilities' => $liabilities, 'equity' => $equity,
            'totalAssets' => $assets->sum('amount'),
            'totalLiabEquity' => $liabilities->sum('amount') + $equity->sum('amount') + $netProfit,
            'netProfit' => $netProfit, 'asOf' => $d,
        ]);
    }

    /** Per-account net activity within a period for a given account type. */
    private function periodActivity(string $type, Carbon $from, Carbon $to, bool $debitPositive = false)
    {
        return Account::type($type)->orderBy('code')->get()->map(function ($a) use ($from, $to, $debitPositive) {
            $q = JournalLine::where('account_id', $a->id)
                ->whereHas('entry', fn ($e) => $e->whereBetween('date', [$from, $to]));
            $debit = (float) $q->sum('debit');
            $credit = (float) (clone $q)->sum('credit');
            $amount = $debitPositive ? $debit - $credit : $credit - $debit;

            return ['account' => $a, 'amount' => round($amount, 2)];
        })->filter(fn ($r) => abs($r['amount']) > 0.001)->values();
    }
}
