<?php

namespace App\Services;

use App\Models\Company;
use App\Models\CreditNote;
use App\Models\DebitNote;
use App\Models\Invoice;
use App\Models\PurchaseBill;
use Carbon\Carbon;

class Gstr3bReportService
{
    /**
     * @return array<string, mixed>
     */
    public function build(int $companyId, string $periodFrom, string $periodTo): array
    {
        $company = Company::findOrFail($companyId);
        $start = Carbon::createFromFormat('Y-m', $periodFrom)->startOfMonth();
        $end = Carbon::createFromFormat('Y-m', $periodTo)->endOfMonth();

        $invoices = Invoice::with('customer')
            ->forCompany($companyId)
            ->whereNotIn('status', ['draft', 'cancelled'])
            ->whereDate('invoice_date', '>=', $start)
            ->whereDate('invoice_date', '<=', $end)
            ->get();

        $creditNotes = CreditNote::forCompany($companyId)
            ->whereNotIn('status', ['draft', 'void'])
            ->whereDate('credit_note_date', '>=', $start)
            ->whereDate('credit_note_date', '<=', $end)
            ->get();

        $purchaseBills = PurchaseBill::forCompany($companyId)
            ->whereNotIn('status', ['draft', 'void'])
            ->whereDate('bill_date', '>=', $start)
            ->whereDate('bill_date', '<=', $end)
            ->get();

        $debitNotes = DebitNote::forCompany($companyId)
            ->whereNotIn('status', ['draft', 'void'])
            ->whereDate('debit_note_date', '>=', $start)
            ->whereDate('debit_note_date', '<=', $end)
            ->get();

        $outward = $this->emptyRow();
        $rcmInward = $this->emptyRow();
        $itcOther = $this->emptyRow();
        $interstateUnreg = $this->emptyRow();

        foreach ($invoices as $invoice) {
            $this->addRow($outward, $invoice);
            if ($invoice->is_igst && ! $this->hasValidGstin($invoice->customer?->gstin)) {
                $this->addRow($interstateUnreg, $invoice);
            }
        }

        foreach ($creditNotes as $note) {
            $this->subtractRow($outward, $note);
            if ($note->is_igst) {
                $this->subtractRow($interstateUnreg, $note);
            }
        }

        foreach ($debitNotes as $note) {
            if ($note->isClient()) {
                $this->addRow($outward, $note);
                if ($note->is_igst) {
                    $this->addRow($interstateUnreg, $note);
                }
            } else {
                $this->subtractRow($itcOther, $note);
            }
        }

        foreach ($purchaseBills as $bill) {
            if ($bill->reverse_charge) {
                $this->addRow($rcmInward, $bill);
            } else {
                $this->addRow($itcOther, $bill);
            }
        }

        $table31 = [
            'a' => $this->roundRow($outward),
            'b' => $this->emptyRow(),
            'c' => $this->emptyRow(),
            'd' => $this->roundRow($rcmInward),
            'e' => $this->emptyRow(),
        ];

        $table4Available = $this->roundRow($itcOther);
        $table4Reversed = $this->emptyRow();
        $table4Net = $this->roundRow([
            'taxable' => 0,
            'igst' => $table4Available['igst'] - $table4Reversed['igst'],
            'cgst' => $table4Available['cgst'] - $table4Reversed['cgst'],
            'sgst' => $table4Available['sgst'] - $table4Reversed['sgst'],
            'cess' => $table4Available['cess'] - $table4Reversed['cess'],
        ]);

        $outwardTax = $this->sumTaxColumns($table31['a'], $table31['d']);
        $table61 = $this->buildTaxPayment($outwardTax, $table4Net);

        return [
            'company' => $company,
            'period_from' => $periodFrom,
            'period_to' => $periodTo,
            'gstin' => strtoupper((string) $company->gstin),
            'ret_period' => $end->format('mY'),
            'table_31' => $table31,
            'table_311' => [
                'i' => $this->emptyRow(),
                'ii' => $this->emptyRow(),
            ],
            'table_32' => [
                'unregistered' => $this->roundRow($interstateUnreg),
                'composition' => $this->emptyRow(),
                'uin' => $this->emptyRow(),
            ],
            'table_4' => [
                'a1' => $this->emptyRow(),
                'a2' => $this->emptyRow(),
                'a3' => $this->emptyRow(),
                'a4' => $this->emptyRow(),
                'a5' => $table4Available,
                'b1' => $table4Reversed,
                'b2' => $this->emptyRow(),
                'net' => $table4Net,
            ],
            'table_5' => [
                'composition' => ['inter' => 0, 'intra' => 0],
                'exempt' => ['inter' => 0, 'intra' => 0],
                'nil' => ['inter' => 0, 'intra' => 0],
                'non_gst' => ['inter' => 0, 'intra' => 0],
            ],
            'table_61' => $table61,
            'docs' => [
                'invoices' => $invoices->count(),
                'credit_notes' => $creditNotes->count(),
                'purchase_bills' => $purchaseBills->count(),
                'debit_notes' => $debitNotes->count(),
            ],
        ];
    }

    /**
     * @return array{taxable: float, igst: float, cgst: float, sgst: float, cess: float}
     */
    private function emptyRow(): array
    {
        return ['taxable' => 0, 'igst' => 0, 'cgst' => 0, 'sgst' => 0, 'cess' => 0];
    }

    private function addRow(array &$target, Invoice|CreditNote|PurchaseBill|DebitNote $doc): void
    {
        $target['taxable'] += (float) $doc->taxable_amount;
        $target['igst'] += (float) $doc->igst_amount;
        $target['cgst'] += (float) $doc->cgst_amount;
        $target['sgst'] += (float) $doc->sgst_amount;
    }

    private function subtractRow(array &$target, CreditNote|DebitNote $doc): void
    {
        $target['taxable'] -= (float) $doc->taxable_amount;
        $target['igst'] -= (float) $doc->igst_amount;
        $target['cgst'] -= (float) $doc->cgst_amount;
        $target['sgst'] -= (float) $doc->sgst_amount;
    }

    /**
     * @param  array{taxable: float, igst: float, cgst: float, sgst: float, cess: float}  $row
     * @return array{taxable: float, igst: float, cgst: float, sgst: float, cess: float}
     */
    private function roundRow(array $row): array
    {
        return [
            'taxable' => round($row['taxable'], 2),
            'igst' => round($row['igst'], 2),
            'cgst' => round($row['cgst'], 2),
            'sgst' => round($row['sgst'], 2),
            'cess' => round($row['cess'], 2),
        ];
    }

    /**
     * @param  array{taxable: float, igst: float, cgst: float, sgst: float, cess: float}  ...$rows
     * @return array{igst: float, cgst: float, sgst: float, cess: float}
     */
    private function sumTaxColumns(array ...$rows): array
    {
        $sum = ['igst' => 0, 'cgst' => 0, 'sgst' => 0, 'cess' => 0];
        foreach ($rows as $row) {
            $sum['igst'] += $row['igst'];
            $sum['cgst'] += $row['cgst'];
            $sum['sgst'] += $row['sgst'];
            $sum['cess'] += $row['cess'];
        }

        return array_map(fn ($v) => round($v, 2), $sum);
    }

    /**
     * @param  array{igst: float, cgst: float, sgst: float, cess: float}  $payable
     * @param  array{taxable: float, igst: float, cgst: float, sgst: float, cess: float}  $itc
     * @return array<string, array{payable: float, itc: float, cash: float}>
     */
    private function buildTaxPayment(array $payable, array $itc): array
    {
        $heads = ['igst', 'cgst', 'sgst', 'cess'];
        $result = [];
        foreach ($heads as $head) {
            $tax = $payable[$head] ?? 0;
            $credit = $itc[$head] ?? 0;
            $result[$head] = [
                'payable' => $tax,
                'itc' => $credit,
                'cash' => round(max(0, $tax - $credit), 2),
            ];
        }

        return $result;
    }

    private function hasValidGstin(?string $gstin): bool
    {
        $gstin = strtoupper(trim((string) $gstin));

        return (bool) preg_match('/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/', $gstin);
    }
}
