<?php

namespace App\Exports;

use App\Helpers\NumberHelper;
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 ProfitLossExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize, WithStyles, WithTitle
{
    private Collection $rows;

    /** @var array<int, array{key: string, label: string}> */
    private array $columnPeriods;

    public function __construct(
        array $groups,
        array $totals,
        int $companyId,
        string $companyName,
        string $periodLabel,
        array $columnPeriods = [],
    ) {
        $this->columnPeriods = $columnPeriods;
        $this->rows = $this->buildRows($groups, $totals, $companyId, $companyName, $periodLabel);
    }

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

    public function headings(): array
    {
        if ($this->columnPeriods === []) {
            return ['Account', 'Amount'];
        }

        return array_merge(
            ['Account'],
            array_column($this->columnPeriods, 'label'),
            ['Total'],
        );
    }

    public function map($row): array
    {
        if ($this->columnPeriods === []) {
            return [$row['account'], $row['amount']];
        }

        return array_merge(
            [$row['account']],
            array_map(
                fn (array $period) => $row['amounts'][$period['key']] ?? '—',
                $this->columnPeriods,
            ),
            [$row['amount']],
        );
    }

    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');

        $lastColumn = $this->columnPeriods === []
            ? 'B'
            : chr(ord('A') + count($this->columnPeriods) + 1);

        if ($lastColumn !== 'A') {
            $sheet->getStyle("B:{$lastColumn}")->getAlignment()->setHorizontal('right');
        }

        return [];
    }

    public function title(): string
    {
        return 'Profit & Loss';
    }

    private function buildRows(
        array $groups,
        array $totals,
        int $companyId,
        string $companyName,
        string $periodLabel,
    ): Collection {
        $blankAmounts = $this->blankAmounts();
        $rows = collect([
            ['account' => $companyName, 'amount' => '', 'amounts' => $blankAmounts],
            ['account' => 'Profit & Loss', 'amount' => '', 'amounts' => $blankAmounts],
            ['account' => $periodLabel, 'amount' => '', 'amounts' => $blankAmounts],
            ['account' => '', 'amount' => '', 'amounts' => $blankAmounts],
        ]);

        $format = function (float $amount) use ($companyId): string {
            if (abs($amount) < 0.005) {
                return '—';
            }

            $formatted = NumberHelper::formatAmount(abs($amount), $companyId);

            return $amount < 0 ? '(' . $formatted . ')' : $formatted;
        };

        $formatAmounts = function (array $amounts) use ($format): array {
            $formatted = [];
            foreach ($this->columnPeriods as $period) {
                $formatted[$period['key']] = $format((float) ($amounts[$period['key']] ?? 0));
            }

            return $formatted;
        };

        $appendSection = function (array $group) use ($rows, $format, $formatAmounts, $blankAmounts): void {
            $rows->push(['account' => strtoupper($group['label']), 'amount' => '', 'amounts' => $blankAmounts]);
            $this->appendTree($group['tree'] ?? [], $rows, $format, $formatAmounts, $blankAmounts, 0);
            $rows->push([
                'account' => 'Total ' . $group['label'],
                'amount' => $format((float) $group['total']),
                'amounts' => $this->columnPeriods === []
                    ? $blankAmounts
                    : $formatAmounts($group['period_totals'] ?? []),
            ]);
        };

        foreach (['income', 'cost_of_goods_sold'] as $section) {
            $appendSection($groups[$section]);
        }

        $rows->push([
            'account' => 'Gross Profit',
            'amount' => $format((float) $totals['gross_profit']),
            'amounts' => $this->columnPeriods === []
                ? $blankAmounts
                : $formatAmounts($totals['periods']['gross_profit'] ?? []),
        ]);

        $appendSection($groups['expenses']);

        $rows->push([
            'account' => 'Net Operating Income',
            'amount' => $format((float) $totals['operating_income']),
            'amounts' => $this->columnPeriods === []
                ? $blankAmounts
                : $formatAmounts($totals['periods']['operating_income'] ?? []),
        ]);

        foreach (['other_income', 'other_expense'] as $section) {
            $appendSection($groups[$section]);
        }

        $rows->push([
            'account' => 'Net Income',
            'amount' => $format((float) $totals['net_income']),
            'amounts' => $this->columnPeriods === []
                ? $blankAmounts
                : $formatAmounts($totals['periods']['net_income'] ?? []),
        ]);

        return $rows;
    }

    private function appendTree(
        array $nodes,
        Collection $rows,
        callable $format,
        callable $formatAmounts,
        array $blankAmounts,
        int $level,
    ): void {
        foreach ($nodes as $node) {
            $label = trim(($node->code ? $node->code . ' ' : '') . $node->name);
            $rows->push([
                'account' => str_repeat('  ', $level) . $label,
                'amount' => $format((float) $node->report_amount),
                'amounts' => $this->columnPeriods === []
                    ? $blankAmounts
                    : $formatAmounts($node->report_period_amounts ?? []),
            ]);

            if (!empty($node->children_tree)) {
                $this->appendTree($node->children_tree, $rows, $format, $formatAmounts, $blankAmounts, $level + 1);
            }
        }
    }

    private function blankAmounts(): array
    {
        $amounts = [];
        foreach ($this->columnPeriods as $period) {
            $amounts[$period['key']] = '';
        }

        return $amounts;
    }
}
