<?php

namespace App\Services;

use App\Helpers\GstStateHelper;
use App\Models\Company;
use App\Models\CreditNote;
use App\Models\CreditNoteItem;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use Carbon\Carbon;
use Illuminate\Support\Collection;

class Gstr1ReportService
{
    /**
     * @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', 'items'])
            ->forCompany($companyId)
            ->whereNotIn('status', ['draft', 'cancelled'])
            ->whereDate('invoice_date', '>=', $start)
            ->whereDate('invoice_date', '<=', $end)
            ->orderBy('invoice_date')
            ->get();

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

        $b2bRows = [];
        $b2csBuckets = [];
        $cdnrRows = [];
        $hsnBuckets = [];

        foreach ($invoices as $invoice) {
            $customer = $invoice->customer;
            $gstin = strtoupper(trim((string) ($customer?->gstin ?? '')));
            $pos = GstStateHelper::code($invoice->place_of_supply ?: $customer?->state);
            $lines = $this->invoiceLines($invoice);

            foreach ($lines as $line) {
                $this->accumulateHsn($hsnBuckets, $line);
            }

            if ($this->isValidGstin($gstin)) {
                foreach ($lines as $line) {
                    $b2bRows[] = $this->b2bRow($invoice, $customer?->name, $gstin, $pos, $line);
                }
            } else {
                foreach ($lines as $line) {
                    $key = $pos.'|'.number_format($line['rate'], 2, '.', '');
                    if (! isset($b2csBuckets[$key])) {
                        $b2csBuckets[$key] = [
                            'type' => 'OE',
                            'pos' => $pos,
                            'rate' => $line['rate'],
                            'taxable' => 0,
                            'cess' => 0,
                        ];
                    }
                    $b2csBuckets[$key]['taxable'] += $line['taxable'];
                    $b2csBuckets[$key]['cess'] += $line['cess'];
                }
            }
        }

        foreach ($creditNotes as $note) {
            $customer = $note->customer;
            $gstin = strtoupper(trim((string) ($customer?->gstin ?? '')));
            if (! $this->isValidGstin($gstin)) {
                continue;
            }
            $pos = GstStateHelper::code($note->place_of_supply ?: $customer?->state);
            foreach ($this->creditNoteLines($note) as $line) {
                $cdnrRows[] = [
                    'gstin' => $gstin,
                    'name' => $customer?->name ?? '',
                    'number' => $note->credit_note_number,
                    'date' => $note->credit_note_date->format('d-m-Y'),
                    'note_type' => 'C',
                    'pos' => $pos,
                    'rchrg' => 'N',
                    'supply_type' => 'Regular B2B',
                    'rate' => $line['rate'],
                    'taxable' => round($line['taxable'], 2),
                    'cess' => round($line['cess'], 2),
                ];
            }
        }

        $b2csRows = array_values(array_map(fn ($b) => [
            'type' => $b['type'],
            'pos' => $b['pos'],
            'rate' => $b['rate'],
            'taxable' => round($b['taxable'], 2),
            'cess' => round($b['cess'], 2),
            'ecom' => '',
        ], $b2csBuckets));

        $hsnRows = $this->hsnRowsFromBuckets($hsnBuckets);

        return [
            'company' => $company,
            'period_from' => $periodFrom,
            'period_to' => $periodTo,
            'period_start' => $start,
            'period_end' => $end,
            'ret_period' => $end->format('mY'),
            'gstin' => strtoupper((string) $company->gstin),
            'b2b' => $b2bRows,
            'b2cs' => $b2csRows,
            'cdnr' => $cdnrRows,
            'hsn' => $hsnRows,
            'docs' => [
                'invoices' => $invoices->count(),
                'credit_notes' => $creditNotes->count(),
            ],
            'json' => $this->toGovernmentJson($company, $end, $invoices, $creditNotes, $b2bRows, $b2csRows, $cdnrRows, $hsnRows),
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function invoiceLines(Invoice $invoice): array
    {
        $lines = [];
        foreach ($invoice->items as $item) {
            $lines[] = $this->lineFromItem($item, $invoice->is_igst);
        }

        if ($invoice->has_shipping && (float) $invoice->shipping_charges > 0) {
            $rate = $this->dominantRate($invoice->items);
            $taxable = (float) $invoice->shipping_charges;
            $lines[] = $this->syntheticLine('SHIPPING', 'Shipping & Packaging', 'NA', 1, $taxable, $rate, $invoice->is_igst);
        }

        return $lines;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function creditNoteLines(CreditNote $note): array
    {
        $lines = [];
        foreach ($note->items as $item) {
            $lines[] = $this->lineFromCreditItem($item, $note->is_igst);
        }

        return $lines;
    }

    private function lineFromItem(InvoiceItem $item, bool $isIgst): array
    {
        $qty = (float) $item->quantity;
        $rate = (float) $item->rate;
        $disc = (float) $item->discount_percent;
        $taxable = max(0, ($qty * $rate) - ($qty * $rate * $disc / 100));

        return $this->syntheticLine(
            $item->hsn_sac,
            $item->item_name,
            $item->unit,
            $qty,
            $taxable,
            (float) $item->tax_rate,
            $isIgst,
            (float) $item->cgst_amount,
            (float) $item->sgst_amount,
            (float) $item->igst_amount,
        );
    }

    private function lineFromCreditItem(CreditNoteItem $item, bool $isIgst): array
    {
        $qty = (float) $item->quantity;
        $rate = (float) $item->rate;
        $disc = (float) $item->discount_percent;
        $taxable = max(0, ($qty * $rate) - ($qty * $rate * $disc / 100));

        return $this->syntheticLine(
            $item->hsn_sac,
            $item->item_name,
            $item->unit,
            $qty,
            $taxable,
            (float) $item->tax_rate,
            $isIgst,
            (float) $item->cgst_amount,
            (float) $item->sgst_amount,
            (float) $item->igst_amount,
        );
    }

    /**
     * @return array<string, mixed>
     */
    private function syntheticLine(
        ?string $hsn,
        string $name,
        ?string $unit,
        float $qty,
        float $taxable,
        float $rate,
        bool $isIgst,
        ?float $cgst = null,
        ?float $sgst = null,
        ?float $igst = null,
    ): array {
        if ($cgst === null && $sgst === null && $igst === null) {
            $tax = $taxable * ($rate / 100);
            if ($isIgst) {
                $igst = $tax;
                $cgst = 0;
                $sgst = 0;
            } else {
                $cgst = $tax / 2;
                $sgst = $tax - $cgst;
                $igst = 0;
            }
        }

        return [
            'hsn' => $hsn ?: 'NA',
            'name' => $name,
            'unit' => $unit ?: 'NOS',
            'qty' => $qty,
            'taxable' => round($taxable, 2),
            'rate' => $rate,
            'cgst' => round((float) $cgst, 2),
            'sgst' => round((float) $sgst, 2),
            'igst' => round((float) $igst, 2),
            'cess' => 0,
            'total' => round($taxable + (float) $cgst + (float) $sgst + (float) $igst, 2),
        ];
    }

    private function dominantRate(Collection $items): float
    {
        $max = 0.0;
        $rate = 0.0;
        foreach ($items as $item) {
            $t = (float) $item->quantity * (float) $item->rate;
            if ($t >= $max) {
                $max = $t;
                $rate = (float) $item->tax_rate;
            }
        }

        return $rate;
    }

  /**
     * @param  array<string, mixed>  $line
     * @return array<string, mixed>
     */
    private function b2bRow(Invoice $invoice, ?string $name, string $gstin, string $pos, array $line): array
    {
        return [
            'gstin' => $gstin,
            'name' => $name ?? '',
            'number' => $invoice->invoice_number,
            'date' => $invoice->invoice_date->format('d-m-Y'),
            'value' => $line['total'],
            'pos' => $pos,
            'rchrg' => 'N',
            'inv_type' => 'Regular B2B',
            'rate' => $line['rate'],
            'taxable' => $line['taxable'],
            'cess' => $line['cess'],
        ];
    }

    /**
     * @param  array<string, array<string, mixed>>  $buckets
     * @param  array<string, mixed>  $line
     */
    private function accumulateHsn(array &$buckets, array $line): void
    {
        $hsn = $line['hsn'] ?: 'NA';
        if (! isset($buckets[$hsn])) {
            $buckets[$hsn] = [
                'hsn' => $hsn,
                'desc' => $line['name'],
                'uqc' => $line['unit'] ?: 'NOS',
                'qty' => 0,
                'total' => 0,
                'taxable' => 0,
                'igst' => 0,
                'cgst' => 0,
                'sgst' => 0,
                'cess' => 0,
            ];
        }
        $buckets[$hsn]['qty'] += $line['qty'];
        $buckets[$hsn]['total'] += $line['total'];
        $buckets[$hsn]['taxable'] += $line['taxable'];
        $buckets[$hsn]['igst'] += $line['igst'];
        $buckets[$hsn]['cgst'] += $line['cgst'];
        $buckets[$hsn]['sgst'] += $line['sgst'];
        $buckets[$hsn]['cess'] += $line['cess'];
    }

    /**
     * @param  array<string, array<string, mixed>>  $buckets
     * @return list<array<string, mixed>>
     */
    private function hsnRowsFromBuckets(array $buckets): array
    {
        return array_values(array_map(fn ($b) => [
            'hsn' => $b['hsn'],
            'desc' => $b['desc'],
            'uqc' => $b['uqc'],
            'qty' => round($b['qty'], 3),
            'total' => round($b['total'], 2),
            'taxable' => round($b['taxable'], 2),
            'igst' => round($b['igst'], 2),
            'cgst' => round($b['cgst'], 2),
            'sgst' => round($b['sgst'], 2),
            'cess' => round($b['cess'], 2),
        ], $buckets));
    }

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

    /**
     * @param  list<array<string, mixed>>  $b2bRows
     * @param  list<array<string, mixed>>  $b2csRows
     * @param  list<array<string, mixed>>  $cdnrRows
     * @param  list<array<string, mixed>>  $hsnRows
     * @return array<string, mixed>
     */
    private function toGovernmentJson(
        Company $company,
        Carbon $end,
        Collection $invoices,
        Collection $creditNotes,
        array $b2bRows,
        array $b2csRows,
        array $cdnrRows,
        array $hsnRows,
    ): array {
        $b2bGrouped = [];
        foreach ($b2bRows as $row) {
            $ctin = $row['gstin'];
            $inum = $row['number'];
            if (! isset($b2bGrouped[$ctin])) {
                $b2bGrouped[$ctin] = [];
            }
            if (! isset($b2bGrouped[$ctin][$inum])) {
                $b2bGrouped[$ctin][$inum] = [
                    'inum' => $inum,
                    'idt' => $row['date'],
                    'val' => 0,
                    'pos' => $row['pos'],
                    'rchrg' => $row['rchrg'],
                    'inv_typ' => 'R',
                    'itms' => [],
                ];
            }
            $num = count($b2bGrouped[$ctin][$inum]['itms']) + 1;
            $b2bGrouped[$ctin][$inum]['val'] += $row['taxable'] + ($row['taxable'] * $row['rate'] / 100);
            $b2bGrouped[$ctin][$inum]['itms'][] = [
                'num' => $num,
                'itm_det' => [
                    'txval' => $row['taxable'],
                    'rt' => $row['rate'],
                    'camt' => round($row['taxable'] * $row['rate'] / 200, 2),
                    'samt' => round($row['taxable'] * $row['rate'] / 200, 2),
                    'iamt' => 0,
                ],
            ];
        }

        $b2bJson = [];
        foreach ($b2bGrouped as $ctin => $invs) {
            $b2bJson[] = [
                'ctin' => $ctin,
                'inv' => array_values($invs),
            ];
        }

        return [
            'gstin' => strtoupper((string) $company->gstin),
            'fp' => $end->format('mY'),
            'version' => 'GST3.2.2',
            'hash' => 'hash',
            'b2b' => $b2bJson,
            'b2cs' => array_map(fn ($r) => [
                'typ' => $r['type'],
                'pos' => $r['pos'],
                'rt' => $r['rate'],
                'txval' => $r['taxable'],
                'cess' => $r['cess'],
                'ecom_gstin' => $r['ecom'] ?: '',
            ], $b2csRows),
            'cdnr' => array_map(fn ($r) => [
                'ctin' => $r['gstin'],
                'nt' => [[
                    'nt_num' => $r['number'],
                    'nt_dt' => $r['date'],
                    'ntty' => $r['note_type'],
                    'pos' => $r['pos'],
                    'rchrg' => $r['rchrg'],
                    'val' => $r['taxable'] + ($r['taxable'] * $r['rate'] / 100),
                    'itms' => [[
                        'num' => 1,
                        'itm_det' => [
                            'txval' => $r['taxable'],
                            'rt' => $r['rate'],
                            'camt' => round($r['taxable'] * $r['rate'] / 200, 2),
                            'samt' => round($r['taxable'] * $r['rate'] / 200, 2),
                            'iamt' => 0,
                        ],
                    ]],
                ]],
            ], $cdnrRows),
            'hsn' => [
                'data' => array_map(fn ($h) => [
                    'num' => 1,
                    'hsn_sc' => $h['hsn'],
                    'desc' => $h['desc'],
                    'uqc' => $h['uqc'],
                    'qty' => $h['qty'],
                    'val' => $h['total'],
                    'txval' => $h['taxable'],
                    'iamt' => $h['igst'],
                    'camt' => $h['cgst'],
                    'samt' => $h['sgst'],
                    'csamt' => $h['cess'],
                ], $hsnRows),
            ],
            'doc_issue' => [
                'doc_det' => [
                    ['doc_num' => 1, 'doc_typ' => 'Invoices for outward supply', 'docs' => [['num' => 1, 'from' => '', 'to' => '', 'totnum' => $invoices->count(), 'cancel' => 0, 'net_issue' => $invoices->count()]]],
                ],
            ],
        ];
    }
}
