<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\FinancialAccount;
use App\Services\AdvancedFinancialReportService;
use App\Services\FinancialReportService;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class FinancialReportController extends Controller
{
    public function index(Request $request, FinancialReportService $service, AdvancedFinancialReportService $advanced, ?string $report = null): View
    {
        $report = $report ?: 'profit-loss';
        $reports = array_merge(AdvancedFinancialReportService::REPORTS, FinancialReportService::REPORTS);
        abort_unless(array_key_exists($report, $reports), 404);
        $scope = $this->resolveScope($request);
        $data = isset(AdvancedFinancialReportService::REPORTS[$report])
            ? $advanced->generate($report, $scope['selectedBranchIds']->all(), $scope['from'], $scope['to'])
            : $service->generate($report, $scope['selectedBranchIds']->all(), $scope['from'], $scope['to'], $scope['accountId']);

        return view('reports.index', array_merge($scope, compact('report', 'reports', 'data')));
    }

    public function export(Request $request, FinancialReportService $service, AdvancedFinancialReportService $advanced, string $report): StreamedResponse
    {
        $reports = array_merge(AdvancedFinancialReportService::REPORTS, FinancialReportService::REPORTS);
        abort_unless(array_key_exists($report, $reports), 404);
        $scope = $this->resolveScope($request);
        $data = isset(AdvancedFinancialReportService::REPORTS[$report])
            ? $advanced->generate($report, $scope['selectedBranchIds']->all(), $scope['from'], $scope['to'])
            : $service->generate($report, $scope['selectedBranchIds']->all(), $scope['from'], $scope['to'], $scope['accountId']);
        [$columns, $rows] = $this->csvTable($report, $data);
        $filename = $report.'-'.$scope['from'].'-to-'.$scope['to'].'.csv';
        $title = $reports[$report];
        $branchNames = $scope['selectedBranches']->pluck('name')->join(', ');

        return response()->streamDownload(function () use ($title, $scope, $branchNames, $columns, $rows): void {
            $out = fopen('php://output', 'w');
            fwrite($out, "\xEF\xBB\xBF");
            fputcsv($out, [$title]);
            fputcsv($out, ['From', $scope['from'], 'To', $scope['to']]);
            fputcsv($out, ['Branches', $branchNames]);
            fputcsv($out, []);
            fputcsv($out, $columns);
            foreach ($rows as $row) fputcsv($out, array_map(fn ($value) => $this->csvCell($value), $row));
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv; charset=UTF-8', 'X-Content-Type-Options' => 'nosniff']);
    }

    private function resolveScope(Request $request): array
    {
        $allowedBranchIds = $this->allowedBranchIds($request)->map(fn ($id) => (int) $id)->values();
        abort_if($allowedBranchIds->isEmpty(), 403, 'No branch access is assigned to this user.');

        $validated = validator([
            'from' => $request->input('from', now()->startOfMonth()->toDateString()),
            'to' => $request->input('to', now()->toDateString()),
            'branch_ids' => $request->input('branch_ids', []),
            'account_id' => $request->input('account_id'),
        ], [
            'from' => ['required', 'date'],
            'to' => ['required', 'date', 'after_or_equal:from'],
            'branch_ids' => ['array'],
            'branch_ids.*' => ['integer', 'exists:branches,id'],
            'account_id' => ['nullable', 'integer', 'exists:financial_accounts,id'],
        ])->validate();

        $requestedBranchIds = collect($validated['branch_ids'] ?? [])->map(fn ($id) => (int) $id)->unique()->values();
        if ($requestedBranchIds->diff($allowedBranchIds)->isNotEmpty()) abort(403, 'One or more selected branches are outside your assigned scope.');
        $selectedBranchIds = $requestedBranchIds->isEmpty() ? $allowedBranchIds : $requestedBranchIds;

        $accountId = isset($validated['account_id']) ? (int) $validated['account_id'] : null;
        if ($accountId) {
            $account = FinancialAccount::findOrFail($accountId);
            abort_unless($selectedBranchIds->contains((int) $account->branch_id), 403, 'The selected account is outside the report branch scope.');
        }

        $from = CarbonImmutable::parse((string) $validated['from'])->toDateString();
        $to = CarbonImmutable::parse((string) $validated['to'])->toDateString();
        $branches = Branch::whereIn('id', $allowedBranchIds)->orderBy('name')->get();
        $accounts = FinancialAccount::with('branch')->whereIn('branch_id', $selectedBranchIds)->orderBy('branch_id')->orderBy('name')->get();
        $selectedBranches = $branches->whereIn('id', $selectedBranchIds)->values();

        return compact('branches', 'accounts', 'selectedBranchIds', 'selectedBranches', 'accountId', 'from', 'to');
    }

    private function csvTable(string $report, array $data): array
    {
        return match ($report) {
            'trial-balance' => [[
                'Account / Ledger Category','Opening Balance','Period Debit','Period Credit','Closing Debit','Closing Credit',
            ], collect($data['rows'])->map(fn ($row) => [
                $row['label'],$row['opening_balance'],$row['period_debit'],$row['period_credit'],$row['debit_balance'],$row['credit_balance'],
            ])->push(['TOTAL','',$data['period_debits'],$data['period_credits'],$data['debit_balances'],$data['credit_balances']])->all()],
            'income-statement' => $this->incomeStatementCsv($data),
            'statement-of-financial-position' => $this->financialPositionCsv($data),
            'portfolio-summary' => [[
                'Branch','Active Loans','Principal Outstanding','Total Outstanding','Overdue','PAR %',
            ], collect($data['branch_rows'])->map(fn ($row) => [
                $row['branch']->name,$row['active_loans'],$row['principal_outstanding'],$row['total_outstanding'],$row['overdue'],$row['par_percent'],
            ])->all()],
            'profit-loss' => $this->profitLossCsv($data),
            'cash-flow' => [[
                'Movement', 'Transactions', 'Money In', 'Money Out', 'Net',
            ], collect($data['rows'])->map(fn ($row) => [$row['label'], $row['count'], $row['money_in'], $row['money_out'], $row['net']])->all()],
            'account-statement' => $this->accountStatementCsv($data),
            'capital' => $this->transactionCsv($data['rows'], 'money_in'),
            'expenses' => [[
                'Date', 'Expense Number', 'Branch', 'Category', 'Description', 'Supplier', 'Account', 'Amount', 'Journal',
            ], collect($data['rows'])->map(fn ($expense) => [
                $expense->expense_date->format('Y-m-d'), $expense->expense_number, $expense->branch->name,
                $expense->category?->name, $expense->description, $expense->supplier, $expense->financialAccount?->name,
                $expense->amount, $expense->accounting_journal_number,
            ])->all()],
            'payroll' => [[
                'Paid At', 'Payslip', 'Employee', 'Branch', 'Period', 'Gross', 'Deductions', 'Net',
            ], collect($data['rows'])->map(fn ($entry) => [
                $entry->paid_at?->format('Y-m-d H:i:s'), $entry->payslip_number, $entry->employee_name_snapshot,
                $entry->branch?->name, $entry->period?->period_number, $entry->gross, $entry->total_deductions, $entry->net,
            ])->all()],
            'collections' => $this->transactionCsv($data['rows'], 'money_in'),
            'disbursements' => $this->transactionCsv($data['rows'], 'money_out'),
        };
    }

    private function incomeStatementCsv(array $data): array
    {
        $rows = [];
        foreach ($data['income_rows'] as $row) $rows[] = ['Income', $row['label'], $row['amount']];
        $rows[] = ['Income', 'Total Income', $data['total_income']];
        foreach ($data['expense_rows'] as $row) $rows[] = ['Expense', $row['label'], $row['amount']];
        $rows[] = ['Expense', 'Total Expenses', $data['total_expenses']];
        $rows[] = ['Result', 'Net Profit / Loss', $data['net_profit']];
        return [['Section','Category','Amount'], $rows];
    }

    private function financialPositionCsv(array $data): array
    {
        $rows = [];
        foreach ($data['asset_rows'] as $row) $rows[] = ['Assets', $row['label'], $row['amount']];
        $rows[] = ['Assets', 'Total Assets', $data['total_assets']];
        $rows[] = ['Liabilities', 'Total Liabilities', $data['total_liabilities']];
        foreach ($data['equity_rows'] as $row) $rows[] = ['Equity', $row['label'], $row['amount']];
        $rows[] = ['Equity', 'Total Equity', $data['total_equity']];
        $rows[] = ['Check', 'Accounting Difference', $data['difference']];
        return [['Section','Line Item','Amount'], $rows];
    }

    private function profitLossCsv(array $data): array
    {
        $rows = [];
        foreach ($data['income_rows'] as $row) $rows[] = ['Income', $row['label'], $row['amount']];
        $rows[] = ['Income', 'Total Income', $data['total_income']];
        foreach ($data['expense_rows'] as $row) $rows[] = ['Expense', $row['label'], $row['amount']];
        $rows[] = ['Expense', 'Total Expenses', $data['total_expenses']];
        $rows[] = ['Result', 'Net Profit / Loss', $data['net_profit']];
        $rows[] = ['Excluded', 'Loan Principal Activity', $data['principal_activity_excluded']];
        return [['Section', 'Category', 'Amount'], $rows];
    }

    private function accountStatementCsv(array $data): array
    {
        if ($data['selected_account_id']) {
            return [[
                'Date', 'Transaction', 'Reference', 'Type', 'Description', 'Money In', 'Money Out', 'Balance',
            ], collect($data['rows'])->map(function ($row): array {
                $transaction = $row['transaction'];
                return [
                    $transaction->transaction_date->format('Y-m-d H:i:s'), $transaction->transaction_number,
                    $transaction->reference, $transaction->type, $transaction->description,
                    $transaction->money_in, $transaction->money_out, $row['report_balance'],
                ];
            })->all()];
        }

        return [[
            'Account', 'Branch', 'Opening', 'Money In', 'Money Out', 'Closing', 'Transactions',
        ], collect($data['summaries'])->map(fn ($summary) => [
            $summary['account']->name, $summary['account']->branch->name, $summary['opening_balance'],
            $summary['money_in'], $summary['money_out'], $summary['closing_balance'], $summary['transaction_count'],
        ])->all()];
    }

    private function transactionCsv($rows, string $amountField): array
    {
        return [[
            'Date', 'Transaction', 'Branch', 'Account', 'Type', 'Reference', 'Description', 'Amount',
        ], collect($rows)->map(fn ($transaction) => [
            $transaction->transaction_date->format('Y-m-d H:i:s'), $transaction->transaction_number,
            $transaction->branch?->name, $transaction->account?->name, $transaction->type,
            $transaction->reference, $transaction->description, $transaction->{$amountField},
        ])->all()];
    }

    private function allowedBranchIds(Request $request)
    {
        return collect($request->user()->accessibleBranchIds());
    }

    private function csvCell(mixed $value): string
    {
        $text = (string) ($value ?? '');
        return preg_match('/^[=+\-@]/', $text) ? "'".$text : $text;
    }
}
