<?php

namespace App\Http\Controllers\Reports;

use App\Exports\CashLedgerExport;
use App\Http\Controllers\Controller;
use App\Models\Deposit;
use App\Models\Expense;
use App\Models\Payment;
use App\Models\PaymentAccount;
use App\Models\PurchasePayment;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Facades\Excel;

class CashLedgerController extends Controller
{
    public function index(Request $request)
    {
        $data = $this->buildLedger($request);

        return view('pages.reports.cash-ledger', $data + [
            'title' => 'Laporan Buku Besar',
        ]);
    }

    public function print(Request $request)
    {
        $data = $this->buildLedger($request);

        return view('pages.reports.cash-ledger', $data + [
            'title' => 'Laporan Buku Besar',
        ]);
    }

    public function export(Request $request)
    {
        $data = $this->buildLedger($request);

        $start = $data['startDate'] ?? $request->get('start_date');
        $end   = $data['endDate'] ?? $request->get('end_date');

        $suffix = ($start || $end) ? "_{$start}_{$end}" : '';
        $filename = "cash-ledger{$suffix}.xlsx";

        return Excel::download(new CashLedgerExport($data['transactions']), $filename);
    }

    private function buildLedger(Request $request): array
    {
        $today = now()->toDateString();

        $startDate = $request->get('start_date', $today);
        $endDate = $request->get('end_date', $today);
        $selectedAccount = $request->get('payment_account_id');

        $accounts = class_exists(PaymentAccount::class)
            ? PaymentAccount::query()->orderBy('name')->get()
            : collect();

        $saldoAwal = $this->calculateOpeningBalance($startDate, $selectedAccount);

        $transactions = $this->collectTransactions(
            startDate: $startDate,
            endDate: $endDate,
            selectedAccount: $selectedAccount,
            accounts: $accounts
        );

        $transactions = $transactions
            ->sortBy(fn ($trx) => ($trx['tanggal'] ?? '9999-12-31 23:59:59') . '|' . str_pad((string) ($trx['sort_order'] ?? 999), 3, '0', STR_PAD_LEFT))
            ->values();

        $runningSaldo = (float) $saldoAwal;

        $transactions = $transactions->map(function ($trx) use (&$runningSaldo) {
            $debit = (float) ($trx['debit'] ?? 0);
            $kredit = (float) ($trx['kredit'] ?? 0);

            $runningSaldo += ($debit - $kredit);

            $trx['saldo'] = $runningSaldo;
            $trx['balance'] = $runningSaldo;

            return $trx;
        });

        return [
            'transactions' => $transactions,
            'saldoAwal' => $saldoAwal,
            'accounts' => $accounts,
            'startDate' => $startDate,
            'endDate' => $endDate,
        ];
    }

    private function calculateOpeningBalance(string $startDate, $selectedAccount): float
    {
        $saldoAwal = 0.0;

        $paymentQuery = Payment::query()
            ->whereDate('created_at', '<', $startDate);

        if ($selectedAccount === 'cash') {
            $paymentQuery->whereNull('payment_account_id');
        } elseif (!blank($selectedAccount)) {
            $paymentQuery->where('payment_account_id', $selectedAccount);
        }

        $saldoAwal += (float) $paymentQuery->sum('amount');

        if (blank($selectedAccount) || $selectedAccount === 'cash') {
            $saldoAwal -= (float) Expense::query()
                ->whereDate('created_at', '<', $startDate)
                ->sum('amount');

            $saldoAwal -= (float) PurchasePayment::query()
                ->whereDate('payment_date', '<', $startDate)
                ->sum('amount');

            $saldoAwal -= (float) Deposit::query()
                ->whereDate('deposit_date', '<', $startDate)
                ->sum('amount');
        }

        return $saldoAwal;
    }

    private function collectTransactions(
        string $startDate,
        string $endDate,
        $selectedAccount,
        Collection $accounts
    ): Collection {
        $rows = collect();

        $paymentsQuery = Payment::query()
            ->with(['order', 'order.customer', 'order.customerRelation'])
            ->whereDate('created_at', '>=', $startDate)
            ->whereDate('created_at', '<=', $endDate);

        if ($selectedAccount === 'cash') {
            $paymentsQuery->whereNull('payment_account_id');
        } elseif (!blank($selectedAccount)) {
            $paymentsQuery->where('payment_account_id', $selectedAccount);
        }

        $payments = $paymentsQuery->get()->map(function ($item) use ($accounts) {
            $rekening = 'Cash (Kas)';

            if (!empty($item->payment_account_id) && $accounts->count()) {
                $rekening = $accounts->firstWhere('id', (int) $item->payment_account_id)->name ?? 'Rekening';
            }

            return $this->makeRow(
                tanggal: optional($item->created_at)->format('Y-m-d H:i:s') ?? now()->format('Y-m-d H:i:s'),
                keterangan: $item->notes ?: 'Pembayaran Order',
                customer: $this->resolvePaymentCustomer($item),
                rekening: $rekening,
                debit: (float) $item->amount,
                kredit: 0.0,
                source: 'Payment',
                sortOrder: 10
            );
        });

        $rows = $rows->merge($payments);

        if (blank($selectedAccount) || $selectedAccount === 'cash') {
            $expenses = Expense::query()
                ->whereDate('created_at', '>=', $startDate)
                ->whereDate('created_at', '<=', $endDate)
                ->get()
                ->map(function ($item) {
                    return $this->makeRow(
                        tanggal: optional($item->created_at)->format('Y-m-d H:i:s') ?? now()->format('Y-m-d H:i:s'),
                        keterangan: $item->description ?: 'Pengeluaran',
                        customer: '-',
                        rekening: 'Cash (Kas)',
                        debit: 0.0,
                        kredit: (float) $item->amount,
                        source: 'Expense',
                        sortOrder: 20
                    );
                });

            $purchasePayments = PurchasePayment::query()
                ->with(['purchase', 'purchase.supplierRelation'])
                ->whereDate('payment_date', '>=', $startDate)
                ->whereDate('payment_date', '<=', $endDate)
                ->get()
                ->map(function ($item) {
                    return $this->makeRow(
                        tanggal: $item->payment_date
                            ? $item->payment_date . ' 12:00:00'
                            : now()->format('Y-m-d 12:00:00'),
                        keterangan: $item->notes ?: 'Pembayaran Pembelian',
                        customer: $this->resolvePurchasePaymentCustomer($item),
                        rekening: 'Cash (Kas)',
                        debit: 0.0,
                        kredit: (float) $item->amount,
                        source: 'Purchase Payment',
                        sortOrder: 30
                    );
                });

            $deposits = Deposit::query()
                ->whereDate('deposit_date', '>=', $startDate)
                ->whereDate('deposit_date', '<=', $endDate)
                ->get()
                ->map(function ($item) {
                    $keterangan = 'Setoran CASH ke BANK';

                    if (!empty($item->reference_number)) {
                        $keterangan .= ' - Ref: ' . $item->reference_number;
                    }

                    if (!empty($item->notes)) {
                        $keterangan .= ' - ' . $item->notes;
                    }

                    return $this->makeRow(
                        tanggal: $item->deposit_date
                            ? $item->deposit_date . ' 12:00:00'
                            : now()->format('Y-m-d 12:00:00'),
                        keterangan: $keterangan,
                        customer: '-',
                        rekening: 'Cash (Kas)',
                        debit: 0.0,
                        kredit: (float) $item->amount,
                        source: 'Deposit',
                        sortOrder: 40
                    );
                });

            $rows = $rows
                ->merge($expenses)
                ->merge($purchasePayments)
                ->merge($deposits);
        }

        return $rows;
    }

    private function resolvePaymentCustomer($payment): string
    {
        $customer =
            data_get($payment, 'order.customer_name')
            ?? data_get($payment, 'order.customer.name')
            ?? data_get($payment, 'order.customerRelation.name')
            ?? data_get($payment, 'customer_name');

        return filled($customer) ? (string) $customer : '-';
    }

    private function resolvePurchasePaymentCustomer($payment): string
    {
        $customer =
            data_get($payment, 'purchase.supplierRelation.name')
            ?? data_get($payment, 'purchase.supplier')
            ?? data_get($payment, 'purchase.supplier_name');

        return filled($customer) ? (string) $customer : '-';
    }

    private function makeRow(
        string $tanggal,
        string $keterangan,
        string $customer,
        string $rekening,
        float $debit,
        float $kredit,
        string $source,
        int $sortOrder
    ): array {
        return [
            'tanggal' => $tanggal,
            'keterangan' => $keterangan,
            'customer' => $customer,
            'rekening' => $rekening,
            'debit' => $debit,
            'kredit' => $kredit,
            'saldo' => 0.0,

            'date' => $tanggal,
            'description' => $keterangan,
            'credit' => $kredit,
            'balance' => 0.0,

            'source' => $source,
            'sort_order' => $sortOrder,
        ];
    }
}