<?php

namespace App\Exports;

use App\Helpers\NumberHelper;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class GeneralLedgerExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize, WithStyles, WithTitle
{
    private Collection $rows;

    public function __construct(
        array $ledger,
        int $companyId,
        string $companyName,
        string $periodLabel,
        string $dateFrom = '',
    ) {
        $this->rows = $this->buildRows($ledger, $companyId, $companyName, $periodLabel, $dateFrom);
    }

    public function collection(): Collection
    {
        return $this->rows;
    }

    public function headings(): array
    {
        return ['Date', 'Voucher', 'Reference / Narration', 'Name', 'Dimension', 'Split Accounts', 'Debit', 'Credit', 'Balance'];
    }

    public function map($row): array
    {
        return $row;
    }

    public function styles(Worksheet $sheet): array
    {
        $sheet->getStyle('1:1')->getFont()->setBold(true);
        $sheet->getStyle('1:1')->getFill()
            ->setFillType(Fill::FILL_SOLID)
            ->getStartColor()
            ->setARGB('FFEFF6FF');
        $sheet->freezePane('A2');

        return [];
    }

    public function title(): string
    {
        return 'General Ledger';
    }

    private function buildRows(array $ledger, int $companyId, string $companyName, string $periodLabel, string $dateFrom): Collection
    {
        $rows = collect();
        $isAllLedger = ($ledger['mode'] ?? 'single') === 'all';
        $groups = $ledger['groups'] ?? [];
        $entries = $ledger['entries'] ?? collect();
        $totals = $ledger['totals'] ?? [];
        $money = fn ($amount) => abs((float) $amount) < 0.005
            ? '—'
            : NumberHelper::formatAmount((float) abs($amount), $companyId);

        $rows->push([$companyName]);
        $rows->push(['General Ledger']);
        $rows->push([$periodLabel]);
        $rows->push([]);

        if ($isAllLedger && $groups !== []) {
            foreach ($groups as $group) {
                $account = $group['account'];
                $accountTitle = $account->code ? '[' . $account->code . '] ' . $account->name : $account->name;
                $groupTotals = $group['totals'] ?? [];

                $rows->push([$accountTitle]);
                $rows->push($this->openingRow($dateFrom, $groupTotals, $money));

                foreach ($group['entries'] ?? [] as $entry) {
                    $rows->push($this->entryRow($entry, $money));
                }

                $rows->push([
                    '',
                    '',
                    'Total ' . $account->name,
                    '',
                    '',
                    '',
                    $money($groupTotals['period_debit'] ?? 0),
                    $money($groupTotals['period_credit'] ?? 0),
                    $this->balanceLabel($groupTotals, 'closing', $money),
                ]);
                $rows->push([]);
            }
        } else {
            if (!$isAllLedger && $dateFrom !== '') {
                $rows->push($this->openingRow($dateFrom, $totals, $money));
            }

            foreach ($entries as $entry) {
                $rows->push($this->entryRow($entry, $money));
            }
        }

        $rows->push([
            '',
            '',
            'Current Period Total',
            '',
            '',
            '',
            $money($totals['period_debit'] ?? 0),
            $money($totals['period_credit'] ?? 0),
            '',
        ]);

        return $rows;
    }

    private function openingRow(string $dateFrom, array $totals, callable $money): array
    {
        return [
            $dateFrom !== '' ? Carbon::parse($dateFrom)->subDay()->format('d M Y') : '',
            '',
            'Opening Balance',
            '',
            '',
            '',
            $money($totals['opening_debit'] ?? 0),
            $money($totals['opening_credit'] ?? 0),
            $this->balanceLabel($totals, 'opening', $money),
        ];
    }

    private function entryRow($entry, callable $money): array
    {
        $voucherLabel = $entry->voucher
            ? ($entry->voucher->voucher_number ?? 'Voucher #' . $entry->voucher_id)
            : 'Voucher #' . $entry->voucher_id;

        $reference = $entry->reference ?: '—';
        if ($entry->narration) {
            $reference .= ($reference === '—' ? '' : "\n") . $entry->narration;
        }

        return [
            $entry->entry_date->format('d M Y'),
            $voucherLabel,
            $reference,
            $entry->party_name ?: '—',
            $entry->dimension_label ?: '—',
            $entry->split_accounts ?: '—',
            $money($entry->debit),
            $money($entry->credit),
            trim($money($entry->running_balance ?? 0) . ' ' . ($entry->balance_side ?? '')),
        ];
    }

    private function balanceLabel(array $totals, string $prefix, callable $money): string
    {
        $debit = (float) ($totals[$prefix . '_debit'] ?? 0);
        $credit = (float) ($totals[$prefix . '_credit'] ?? 0);

        if ($debit > 0) {
            return $money($debit) . ' Dr';
        }

        if ($credit > 0) {
            return $money($credit) . ' Cr';
        }

        return '—';
    }
}
