<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Admin\Concerns\BuildsBankTransactionsReport;
use App\Http\Controllers\Controller;
use App\Models\BankAccount;
use App\Models\Currency;
use Carbon\Carbon;
use Dompdf\Dompdf;
use Dompdf\Options;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class BankingController extends Controller
{
    use BuildsBankTransactionsReport;

    public function overview(): View
    {
        $bankAccounts = BankAccount::orderBy('is_default', 'desc')
            ->orderBy('account_name')
            ->get();

        // Calculate stats
        $totalAccounts = $bankAccounts->count();
        $bankBalance = $bankAccounts->sum('current_balance');
        $cashInHand = 0; // This would come from cash accounts or a separate table
        $totalBalance = $cashInHand + $bankBalance;

        return view('admin.banking.overview', [
            'bankAccounts' => $bankAccounts,
            'totalAccounts' => $totalAccounts,
            'bankBalance' => $bankBalance,
            'cashInHand' => $cashInHand,
            'totalBalance' => $totalBalance,
            'balanceTrend' => $this->computeBalanceTrend(90),
            'balanceTrendUrl' => route('admin.banking.balance-trend'),
        ]);
    }

    /**
     * JSON series for the banking overview balance trend chart (net bank movements vs stored balances).
     */
    public function balanceTrend(Request $request): JsonResponse
    {
        $days = (int) $request->query('days', 90);
        $days = min(366, max(7, $days));

        return response()->json($this->computeBalanceTrend($days));
    }

    public function create(): View
    {
        return view('admin.banking.create');
    }

    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'account_name' => 'required|string|max:255',
            'bank_name' => 'required|string|max:255',
            'branch_name' => 'nullable|string|max:255',
            'account_type' => 'nullable|string|max:255',
            'account_number' => 'required|string|max:255',
            'routing_code' => 'nullable|string|max:255',
            'currency' => 'nullable|string|max:3',
            'opening_balance' => 'nullable|numeric|min:0',
        ]);

        $bankAccount = BankAccount::create([
            'account_name' => $validated['account_name'],
            'bank_name' => $validated['bank_name'],
            'branch_name' => $validated['branch_name'] ?? null,
            'account_number' => $validated['account_number'],
            'bank_code' => $validated['routing_code'] ?? null,
            'currency' => $validated['currency'] ?? \App\Models\Currency::getDefaultCode(),
            'opening_balance' => $validated['opening_balance'] ?? 0,
            'current_balance' => $validated['opening_balance'] ?? 0,
            'is_active' => $request->boolean('is_active', true), // Default to true if not present
            'is_default' => false,
            'created_by' => Auth::id(),
        ]);

        return redirect()->route('admin.banking.overview')
            ->with('success', 'Bank account created successfully.');
    }

    public function edit($id): View
    {
        $bankAccount = BankAccount::findOrFail($id);

        return view('admin.banking.edit', compact('bankAccount'));
    }

    public function update(Request $request, $id): RedirectResponse
    {
        $bankAccount = BankAccount::findOrFail($id);

        $validated = $request->validate([
            'account_name' => 'required|string|max:255',
            'bank_name' => 'required|string|max:255',
            'branch_name' => 'nullable|string|max:255',
            'account_type' => 'nullable|string|max:255',
            'account_number' => 'required|string|max:255',
            'routing_code' => 'nullable|string|max:255',
            'currency' => 'nullable|string|max:3',
            'opening_balance' => 'nullable|numeric|min:0',
        ]);

        $bankAccount->update([
            'account_name' => $validated['account_name'],
            'bank_name' => $validated['bank_name'],
            'branch_name' => $validated['branch_name'] ?? null,
            'account_number' => $validated['account_number'],
            'bank_code' => $validated['routing_code'] ?? null,
            'currency' => $validated['currency'] ?? $bankAccount->currency,
            'opening_balance' => $validated['opening_balance'] ?? $bankAccount->opening_balance,
            'is_active' => $request->boolean('is_active', false), // Default to false if not present
        ]);

        return redirect()->route('admin.banking.overview')
            ->with('success', 'Bank account updated successfully.');
    }

    public function destroy($id): RedirectResponse
    {
        $bankAccount = BankAccount::findOrFail($id);

        // Prevent deletion of default account
        if ($bankAccount->is_default) {
            return redirect()->route('admin.banking.overview')
                ->with('error', 'Cannot delete the default bank account.');
        }

        $label = $bankAccount->account_name.' — '.$bankAccount->bank_name;
        $bankAccount->delete();

        return redirect()->route('admin.banking.overview')
            ->with('bank_account_deleted', $label);
    }

    public function setDefault($id): RedirectResponse
    {
        // Set all bank accounts to not default
        BankAccount::where('is_default', true)->update(['is_default' => false]);

        // Set the selected bank account as default
        $bankAccount = BankAccount::findOrFail($id);
        $bankAccount->is_default = true;
        $bankAccount->save();

        return redirect()->route('admin.banking.overview')
            ->with('success', 'Default bank account updated successfully.');
    }

    public function transactions(Request $request): View
    {
        $bankAccounts = BankAccount::where('is_active', true)
            ->orderBy('is_default', 'desc')
            ->orderBy('account_name')
            ->get();

        return view('admin.banking.transactions.index', [
            'bankAccounts' => $bankAccounts,
        ]);
    }

    /**
     * API: Get transactions with filters (AJAX) — aggregates bank-related activity across the app.
     */
    public function getTransactions(Request $request): JsonResponse
    {
        try {
            $bankAccountId = $request->filled('bank_account_id') ? (int) $request->get('bank_account_id') : null;
            $dateFrom = $request->get('date_from');
            $dateTo = $request->get('date_to');
            $search = trim((string) $request->get('search', ''));
            $perPage = max(1, min(200, (int) $request->get('per_page', 15)));
            $page = max(1, (int) $request->get('page', 1));

            $report = $this->buildBankTransactionsReport($bankAccountId, $dateFrom, $dateTo, $search);
            $sorted = $report['items'];

            $total = $sorted->count();
            $lastPage = max(1, (int) ceil($total / $perPage));
            $page = min($page, $lastPage);
            $offset = ($page - 1) * $perPage;
            $paginatedTransactions = $sorted->slice($offset, $perPage)->values();

            return response()->json([
                'success' => true,
                'transactions' => $paginatedTransactions,
                'summary' => [
                    'total_credit' => $report['total_credit'],
                    'total_debit' => $report['total_debit'],
                    'net' => $report['net'],
                    'currency_hint' => $report['currency_hint'],
                    'note' => $report['note'],
                ],
                'pagination' => [
                    'current_page' => $page,
                    'per_page' => $perPage,
                    'total' => $total,
                    'last_page' => $lastPage,
                ],
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error fetching transactions: '.$e->getMessage(),
                'transactions' => [],
            ], 500);
        }
    }

    /**
     * Export filtered bank transactions as CSV (UTF-8, Excel-friendly BOM).
     */
    public function exportBankTransactionsCsv(Request $request): StreamedResponse
    {
        $bankAccountId = $request->filled('bank_account_id') ? (int) $request->get('bank_account_id') : null;
        $dateFrom = $request->get('date_from');
        $dateTo = $request->get('date_to');
        $search = trim((string) $request->get('search', ''));

        $report = $this->buildBankTransactionsReport($bankAccountId, $dateFrom, $dateTo, $search);
        $filename = 'bank-transactions-'.date('Y-m-d-His').'.csv';

        return response()->streamDownload(function () use ($report) {
            $out = fopen('php://output', 'w');
            if ($out === false) {
                return;
            }
            fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF));
            fputcsv($out, ['Date', 'Bank account', 'Description', 'Reference', 'Related party', 'Source', 'Type', 'Amount', 'Currency']);
            foreach ($report['items'] as $row) {
                fputcsv($out, [
                    (string) $row->transaction_date,
                    (string) ($row->bank_account_name ?? ''),
                    (string) ($row->description ?? ''),
                    (string) ($row->reference_number ?? ''),
                    (string) ($row->related_party ?? ''),
                    (string) ($row->source_label ?? ''),
                    ucfirst((string) ($row->type ?? '')),
                    number_format((float) ($row->amount ?? 0), 2, '.', ''),
                    (string) ($row->currency ?? ''),
                ]);
            }
            fclose($out);
        }, $filename, [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    /**
     * Export filtered bank transactions as PDF (landscape table).
     */
    public function exportBankTransactionsPdf(Request $request): Response
    {
        $bankAccountId = $request->filled('bank_account_id') ? (int) $request->get('bank_account_id') : null;
        $dateFrom = $request->get('date_from');
        $dateTo = $request->get('date_to');
        $search = trim((string) $request->get('search', ''));

        $report = $this->buildBankTransactionsReport($bankAccountId, $dateFrom, $dateTo, $search);

        $publicRoot = realpath(public_path());
        if ($publicRoot === false) {
            $publicRoot = public_path();
        }

        $options = new Options;
        $options->set('isHtml5ParserEnabled', true);
        $options->set('defaultFont', 'DejaVu Sans');
        $options->setChroot([$publicRoot]);

        $dompdf = new Dompdf($options);
        $html = view('admin.banking.transactions.export-pdf', [
            'rows' => $report['items'],
            'summary' => $report,
            'generatedAt' => now()->format('Y-m-d H:i'),
            'filters' => [
                'search' => $search,
                'date_from' => $dateFrom,
                'date_to' => $dateTo,
            ],
        ])->render();

        $dompdf->loadHtml($html);
        $dompdf->setPaper('A4', 'landscape');
        $dompdf->render();

        $filename = 'bank-transactions-'.date('Y-m-d').'.pdf';

        return response($dompdf->output(), 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'attachment; filename="'.$filename.'"',
        ]);
    }

    /**
     * Payment methods treated as bank / account movements for this report.
     *
     * @return list<string>
     */
    private function bankChannelPaymentMethods(): array
    {
        return ['bank_transfer', 'account_transfer', 'cheque', 'check', 'online_payment'];
    }

    private function sqlStringLiteral(string $value): string
    {
        return "'".str_replace("'", "''", $value)."'";
    }

    /**
     * End-of-day total balance trend for bank accounts in the default currency (or all accounts if none match).
     * Reconstructs opening-of-window balance from current balances minus net flows in the window.
     *
     * @return array{success: bool, labels: list<string>, values: list<float>, currency: string, subtitle: string, days: int}
     */
    private function computeBalanceTrend(int $days): array
    {
        $defaultCurrency = Currency::getDefaultCode();
        $allAccounts = BankAccount::query()->orderBy('id')->get();
        $inDefault = $allAccounts->where('currency', $defaultCurrency)->values();

        if ($inDefault->isNotEmpty()) {
            $scopeAccounts = $inDefault;
            $subtitle = 'Total balance — '.$defaultCurrency.' accounts';
        } else {
            $scopeAccounts = $allAccounts;
            $uniqueCurrencies = $scopeAccounts->pluck('currency')->filter()->unique()->count();
            $subtitle = $uniqueCurrencies > 1
                ? 'Total balance — all accounts (mixed currencies; chart is indicative)'
                : 'Total balance — all accounts';
        }

        $accountIds = $scopeAccounts->pluck('id')->map(fn ($id) => (int) $id)->all();

        if ($accountIds === []) {
            return [
                'success' => true,
                'labels' => [],
                'values' => [],
                'currency' => $defaultCurrency,
                'subtitle' => 'Add a bank account to see balance history',
                'days' => $days,
            ];
        }

        $endDate = Carbon::now()->startOfDay();
        $startDate = Carbon::now()->copy()->subDays($days)->startOfDay();

        $rows = $this->fetchBankMovementRowsForTrend(
            $accountIds,
            $startDate->format('Y-m-d'),
            $endDate->format('Y-m-d')
        );

        $netByDate = [];
        foreach ($rows as $row) {
            $d = Carbon::parse($row->transaction_date)->format('Y-m-d');
            if (! isset($netByDate[$d])) {
                $netByDate[$d] = 0.0;
            }
            $amt = (float) $row->amount;
            $netByDate[$d] += $row->type === 'credit' ? $amt : -$amt;
        }

        $endBalance = (float) $scopeAccounts->sum('current_balance');
        $totalNet = array_sum($netByDate);
        $balanceBeforeWindow = $endBalance - $totalNet;

        $labels = [];
        $values = [];
        $running = $balanceBeforeWindow;

        for ($cursor = $startDate->copy(); $cursor->lte($endDate); $cursor->addDay()) {
            $key = $cursor->format('Y-m-d');
            $running += $netByDate[$key] ?? 0.0;
            $labels[] = $cursor->format('M j');
            $values[] = round($running, 2);
        }

        return [
            'success' => true,
            'labels' => $labels,
            'values' => $values,
            'currency' => $defaultCurrency,
            'subtitle' => $subtitle,
            'days' => $days,
        ];
    }

    /**
     * Bank-channel movements affecting the given accounts (same sources as the transactions API).
     *
     * @param  list<int>  $accountIds
     */
    private function fetchBankMovementRowsForTrend(array $accountIds, string $dateFrom, string $dateTo): Collection
    {
        if ($accountIds === []) {
            return collect();
        }

        $channelMethods = $this->bankChannelPaymentMethods();
        $dateEndInclusive = $dateTo.' 23:59:59';
        $rows = collect();

        $paymentsQuery = DB::table('payments')
            ->join('invoices', 'payments.invoice_id', '=', 'invoices.id')
            ->join('customers', 'payments.customer_id', '=', 'customers.id')
            ->where('payments.status', 'completed')
            ->whereIn('payments.payment_method', $channelMethods)
            ->whereNotNull('payments.bank_account_id')
            ->whereIn('payments.bank_account_id', $accountIds)
            ->where('payments.payment_date', '>=', $dateFrom)
            ->where('payments.payment_date', '<=', $dateEndInclusive)
            ->select([
                'payments.payment_date as transaction_date',
                DB::raw("'credit' as type"),
                'payments.amount',
            ]);
        $rows = $rows->concat($paymentsQuery->get());

        $purchasesQuery = DB::table('purchases')
            ->whereNotNull('purchases.bank_account_id')
            ->whereIn('purchases.bank_account_id', $accountIds)
            ->whereIn('purchases.payment_method', ['bank_transfer', 'cheque', 'check'])
            ->where('purchases.purchase_date', '>=', $dateFrom)
            ->where('purchases.purchase_date', '<=', $dateEndInclusive)
            ->select([
                'purchases.purchase_date as transaction_date',
                DB::raw("'debit' as type"),
                'purchases.total as amount',
            ]);
        $rows = $rows->concat($purchasesQuery->get());

        $expensesQuery = DB::table('expenses')
            ->whereIn('expenses.payment_method', $channelMethods)
            ->whereIn('expenses.status', ['paid', 'approved'])
            ->whereNotNull('expenses.bank_account_id')
            ->whereIn('expenses.bank_account_id', $accountIds)
            ->where('expenses.expense_date', '>=', $dateFrom)
            ->where('expenses.expense_date', '<=', $dateEndInclusive)
            ->select([
                'expenses.expense_date as transaction_date',
                DB::raw("'debit' as type"),
                'expenses.total as amount',
            ]);
        $rows = $rows->concat($expensesQuery->get());

        $incomeQuery = DB::table('income')
            ->where('income.status', 'recorded')
            ->whereIn('income.payment_method', $channelMethods)
            ->whereNotNull('income.bank_account_id')
            ->whereIn('income.bank_account_id', $accountIds)
            ->where('income.income_date', '>=', $dateFrom)
            ->where('income.income_date', '<=', $dateEndInclusive)
            ->select([
                'income.income_date as transaction_date',
                DB::raw("'credit' as type"),
                'income.total as amount',
            ]);
        $rows = $rows->concat($incomeQuery->get());

        $supplierPayQuery = DB::table('supplier_payments')
            ->leftJoin('purchases', 'supplier_payments.purchase_id', '=', 'purchases.id')
            ->whereIn(DB::raw('COALESCE(supplier_payments.bank_account_id, purchases.bank_account_id)'), $accountIds)
            ->whereNotNull(DB::raw('COALESCE(supplier_payments.bank_account_id, purchases.bank_account_id)'))
            ->where('supplier_payments.payment_date', '>=', $dateFrom)
            ->where('supplier_payments.payment_date', '<=', $dateEndInclusive)
            ->select([
                'supplier_payments.payment_date as transaction_date',
                DB::raw("'debit' as type"),
                'supplier_payments.paid_amount as amount',
            ]);
        $rows = $rows->concat($supplierPayQuery->get());

        $pettyQuery = DB::table('petty_cash_transactions as pct')
            ->where('pct.type', 'top_up')
            ->whereNotNull('pct.bank_account_id')
            ->whereIn('pct.bank_account_id', $accountIds)
            ->whereIn('pct.status', ['approved', 'pending'])
            ->where('pct.transaction_date', '>=', $dateFrom)
            ->where('pct.transaction_date', '<=', $dateEndInclusive)
            ->select([
                'pct.transaction_date',
                DB::raw("'debit' as type"),
                'pct.amount',
            ]);
        $rows = $rows->concat($pettyQuery->get());

        return $rows;
    }
}
