<?php

namespace Modules\MedAccounting\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\MedAccounting\Entities\MedaccAccount;
use Modules\MedAccounting\Entities\MedaccCostCenter;
use Modules\MedAccounting\Services\LedgerService;

/**
 * GL reports (SIFA P7-T2) — plain aggregate queries over posted journal lines, no
 * heavy libs. Each report has a date/period filter. Guarded by
 * medacc.reports.view.
 *
 *   - Trial Balance     — every account's debit/credit; total debit == total credit.
 *   - General Ledger    — one account's posted lines with a running balance.
 *   - Income Statement  — revenue − expense for a period.
 *   - Balance Sheet     — assets == liabilities + equity (as of a date).
 *   - Departmental P&L  — revenue/expense per cost centre for a period.
 */
class ReportController extends MedAccountingBaseController
{
    protected LedgerService $ledger;

    public function __construct(LedgerService $ledger)
    {
        $this->ledger = $ledger;
    }

    /** Landing page with links + shared date filter. */
    public function index()
    {
        $this->guard('medacc.reports.view');

        return view('medaccounting::reports.index', $this->range(request()));
    }

    public function trialBalance(Request $request)
    {
        $this->guard('medacc.reports.view');
        $range = $this->range($request);

        $businessId = $this->businessId();
        $balances = $this->ledger->balance($businessId, $range['from'], $range['to']);
        $accounts = MedaccAccount::forBusiness($businessId)->orderBy('code')->get();

        $rows = [];
        $totalDebit = 0.0;
        $totalCredit = 0.0;

        foreach ($accounts as $acc) {
            $b = $balances[$acc->id] ?? ['debit' => 0.0, 'credit' => 0.0];
            $net = round($b['debit'] - $b['credit'], 4);
            if ($net === 0.0) {
                continue; // suppress zero-activity accounts
            }

            // Present the net on its natural side.
            $debit = $net > 0 ? $net : 0.0;
            $credit = $net < 0 ? -$net : 0.0;

            $rows[] = [
                'code'   => $acc->code,
                'name'   => $acc->name,
                'type'   => $acc->type,
                'debit'  => $debit,
                'credit' => $credit,
            ];
            $totalDebit += $debit;
            $totalCredit += $credit;
        }

        return view('medaccounting::reports.trial_balance', array_merge($range, [
            'rows'        => $rows,
            'totalDebit'  => round($totalDebit, 2),
            'totalCredit' => round($totalCredit, 2),
            'balanced'    => round($totalDebit - $totalCredit, 2) === 0.0,
        ]));
    }

    public function generalLedger(Request $request)
    {
        $this->guard('medacc.reports.view');
        $range = $this->range($request);
        $businessId = $this->businessId();

        $accounts = MedaccAccount::forBusiness($businessId)->orderBy('code')
            ->get(['id', 'code', 'name', 'type'])
            ->mapWithKeys(fn ($a) => [$a->id => $a->code . ' — ' . $a->name])
            ->toArray();

        $accountId = (int) $request->get('account_id', 0);
        $account = $accountId ? MedaccAccount::forBusiness($businessId)->find($accountId) : null;

        $lines = collect();
        $opening = 0.0;
        $running = 0.0;

        if ($account) {
            // Opening balance = signed balance strictly before 'from'.
            if ($range['from']) {
                $before = $this->ledger->balance($businessId, null, Carbon::parse($range['from'])->subDay()->toDateString())[$account->id]
                    ?? ['debit' => 0.0, 'credit' => 0.0];
                $opening = $account->signedBalance($before['debit'], $before['credit']);
            }
            $running = $opening;

            $lines = DB::table('medacc_journal_lines as jl')
                ->join('medacc_journal_entries as je', 'jl.journal_entry_id', '=', 'je.id')
                ->where('jl.business_id', $businessId)
                ->where('jl.account_id', $account->id)
                ->where('je.status', 'posted')
                ->when($range['from'], fn ($q) => $q->whereDate('je.date', '>=', $range['from']))
                ->when($range['to'], fn ($q) => $q->whereDate('je.date', '<=', $range['to']))
                ->orderBy('je.date')->orderBy('je.id')->orderBy('jl.id')
                ->select('je.entry_no', 'je.date', 'je.description as je_desc', 'jl.debit', 'jl.credit', 'jl.description as line_desc')
                ->get()
                ->map(function ($r) use (&$running, $account) {
                    $delta = $account->isDebitNature()
                        ? (float) $r->debit - (float) $r->credit
                        : (float) $r->credit - (float) $r->debit;
                    $running = round($running + $delta, 4);
                    $r->running = $running;

                    return $r;
                });
        }

        return view('medaccounting::reports.general_ledger', array_merge($range, [
            'accounts'  => $accounts,
            'accountId' => $accountId,
            'account'   => $account,
            'opening'   => round($opening, 2),
            'closing'   => round($running, 2),
            'lines'     => $lines,
        ]));
    }

    public function incomeStatement(Request $request)
    {
        $this->guard('medacc.reports.view');
        $range = $this->range($request);
        $businessId = $this->businessId();

        $balances = $this->ledger->balance($businessId, $range['from'], $range['to']);
        $accounts = MedaccAccount::forBusiness($businessId)
            ->whereIn('type', ['revenue', 'expense'])
            ->orderBy('code')->get();

        $revenue = [];
        $expense = [];
        $totalRevenue = 0.0;
        $totalExpense = 0.0;

        foreach ($accounts as $acc) {
            $b = $balances[$acc->id] ?? ['debit' => 0.0, 'credit' => 0.0];
            $amount = $acc->signedBalance($b['debit'], $b['credit']);
            if ($amount === 0.0) {
                continue;
            }
            $row = ['code' => $acc->code, 'name' => $acc->name, 'amount' => round($amount, 2)];
            if ($acc->type === 'revenue') {
                $revenue[] = $row;
                $totalRevenue += $amount;
            } else {
                $expense[] = $row;
                $totalExpense += $amount;
            }
        }

        return view('medaccounting::reports.income_statement', array_merge($range, [
            'revenue'      => $revenue,
            'expense'      => $expense,
            'totalRevenue' => round($totalRevenue, 2),
            'totalExpense' => round($totalExpense, 2),
            'netIncome'    => round($totalRevenue - $totalExpense, 2),
        ]));
    }

    public function balanceSheet(Request $request)
    {
        $this->guard('medacc.reports.view');
        $range = $this->range($request);
        $businessId = $this->businessId();

        // Balance sheet is "as of" the 'to' date (no lower bound — cumulative).
        $asOf = $range['to'] ?: Carbon::now()->toDateString();
        $balances = $this->ledger->balance($businessId, null, $asOf);

        $accounts = MedaccAccount::forBusiness($businessId)
            ->whereIn('type', ['asset', 'liability', 'equity', 'revenue', 'expense'])
            ->orderBy('code')->get();

        $assets = [];
        $liabilities = [];
        $equity = [];
        $totalAssets = 0.0;
        $totalLiabilities = 0.0;
        $totalEquity = 0.0;
        $retainedEarnings = 0.0; // revenue - expense rolls into equity

        foreach ($accounts as $acc) {
            $b = $balances[$acc->id] ?? ['debit' => 0.0, 'credit' => 0.0];
            $amount = $acc->signedBalance($b['debit'], $b['credit']);

            if ($acc->type === 'revenue') {
                $retainedEarnings += $amount;
                continue;
            }
            if ($acc->type === 'expense') {
                $retainedEarnings -= $amount;
                continue;
            }
            if ($amount === 0.0) {
                continue;
            }

            $row = ['code' => $acc->code, 'name' => $acc->name, 'amount' => round($amount, 2)];
            if ($acc->type === 'asset') {
                $assets[] = $row;
                $totalAssets += $amount;
            } elseif ($acc->type === 'liability') {
                $liabilities[] = $row;
                $totalLiabilities += $amount;
            } else { // equity
                $equity[] = $row;
                $totalEquity += $amount;
            }
        }

        // Net income to date is presented as retained earnings inside equity.
        if (round($retainedEarnings, 4) !== 0.0) {
            $equity[] = ['code' => '—', 'name' => __('medaccounting::lang.retained_earnings'), 'amount' => round($retainedEarnings, 2)];
            $totalEquity += $retainedEarnings;
        }

        return view('medaccounting::reports.balance_sheet', array_merge($range, [
            'asOf'             => $asOf,
            'assets'           => $assets,
            'liabilities'      => $liabilities,
            'equity'           => $equity,
            'totalAssets'      => round($totalAssets, 2),
            'totalLiabilities' => round($totalLiabilities, 2),
            'totalEquity'      => round($totalEquity, 2),
            'balanced'         => round($totalAssets - ($totalLiabilities + $totalEquity), 2) === 0.0,
        ]));
    }

    public function departmental(Request $request)
    {
        $this->guard('medacc.reports.view');
        $range = $this->range($request);
        $businessId = $this->businessId();

        $costCenters = MedaccCostCenter::forBusiness($businessId)->orderBy('name')->get();
        $accountTypes = MedaccAccount::forBusiness($businessId)->pluck('type', 'id')->toArray();

        $rows = [];
        foreach ($costCenters as $cc) {
            $balances = $this->ledger->balance($businessId, $range['from'], $range['to'], $cc->id);

            $revenue = 0.0;
            $expense = 0.0;
            foreach ($balances as $accId => $b) {
                $type = $accountTypes[$accId] ?? null;
                if ($type === 'revenue') {
                    $revenue += ($b['credit'] - $b['debit']);
                } elseif ($type === 'expense') {
                    $expense += ($b['debit'] - $b['credit']);
                }
            }

            if (round($revenue, 4) === 0.0 && round($expense, 4) === 0.0) {
                continue;
            }

            $rows[] = [
                'name'    => $cc->name,
                'type'    => $cc->type,
                'revenue' => round($revenue, 2),
                'expense' => round($expense, 2),
                'net'     => round($revenue - $expense, 2),
            ];
        }

        return view('medaccounting::reports.departmental', array_merge($range, [
            'rows' => $rows,
        ]));
    }

    /**
     * Resolve the from/to date filter (defaults: current month start → today).
     *
     * @return array{from:?string,to:?string}
     */
    private function range(Request $request): array
    {
        $from = $request->get('from');
        $to = $request->get('to');

        if (empty($from) && empty($to)) {
            $from = Carbon::now()->startOfMonth()->toDateString();
            $to = Carbon::now()->toDateString();
        }

        return [
            'from' => $from ? Carbon::parse($from)->toDateString() : null,
            'to'   => $to ? Carbon::parse($to)->toDateString() : null,
        ];
    }
}
