<?php
declare(strict_types=1);

namespace App\Helpers;

use App\Config\Database;
use PDO;

/**
 * Advanced GST Calculation Engine & Formatting
 */
class GSTHelper
{

    /**
     * Determine if the transaction is Inter-State or Intra-State
     */
    public static function isInterstate(?string $businessStateCode, ?string $customerStateCode): bool
    {
        return (string)($businessStateCode ?? '') !== (string)($customerStateCode ?? '');
    }

    /**
     * Validate GSTIN Format and return state info
     */
    public static function validate(string $gstin): array
    {
        $gstin = strtoupper(trim($gstin));
        if (strlen($gstin) !== 15) {
            return ['valid' => false, 'message' => 'GSTIN must be 15 characters long'];
        }

        if (!preg_match("/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/", $gstin)) {
            return ['valid' => false, 'message' => 'Invalid GSTIN format'];
        }

        $stateCode = substr($gstin, 0, 2);
        $states = [
            '01' => 'Jammu & Kashmir',
            '02' => 'Himachal Pradesh',
            '03' => 'Punjab',
            '04' => 'Chandigarh',
            '05' => 'Uttarakhand',
            '06' => 'Haryana',
            '07' => 'Delhi',
            '08' => 'Rajasthan',
            '09' => 'Uttar Pradesh',
            '10' => 'Bihar',
            '11' => 'Sikkim',
            '12' => 'Arunachal Pradesh',
            '13' => 'Nagaland',
            '14' => 'Manipur',
            '15' => 'Mizoram',
            '16' => 'Tripura',
            '17' => 'Meghalaya',
            '18' => 'Assam',
            '19' => 'West Bengal',
            '20' => 'Jharkhand',
            '21' => 'Odisha',
            '22' => 'Chhattisgarh',
            '23' => 'Madhya Pradesh',
            '24' => 'Gujarat',
            '26' => 'Dadra and Nagar Haveli and Daman and Diu',
            '27' => 'Maharashtra',
            '29' => 'Karnataka',
            '30' => 'Goa',
            '31' => 'Lakshadweep',
            '32' => 'Kerala',
            '33' => 'Tamil Nadu',
            '34' => 'Puducherry',
            '35' => 'Andaman and Nicobar Islands',
            '36' => 'Telangana',
            '37' => 'Andhra Pradesh',
            '38' => 'Ladakh'
        ];

        return [
            'valid' => true,
            'gstin' => $gstin,
            'state_code' => $stateCode,
            'state_name' => $states[$stateCode] ?? 'Unknown State'
        ];
    }

    /**
     * Calculate taxes for a single line item
     */
    public static function calculateLineItem(
        float $unitPrice,
        float $quantity,
        float $discountPct,
        float $taxRate,
        bool $isInterstate,
        float $cessRate = 0
    ): array {
        $taxableAmount = ($unitPrice * $quantity);
        $discountAmount = ($taxableAmount * $discountPct) / 100;
        $taxableAmount -= $discountAmount;

        $cgstRate = 0;
        $cgstAmount = 0;
        $sgstRate = 0;
        $sgstAmount = 0;
        $igstRate = 0;
        $igstAmount = 0;

        if ($isInterstate) {
            $igstRate = $taxRate;
            $igstAmount = ($taxableAmount * $igstRate) / 100;
        } else {
            $cgstRate = $taxRate / 2;
            $sgstRate = $taxRate / 2;
            $cgstAmount = ($taxableAmount * $cgstRate) / 100;
            $sgstAmount = ($taxableAmount * $sgstRate) / 100;
        }

        $cessAmount = ($taxableAmount * $cessRate) / 100;
        $totalTax = $cgstAmount + $sgstAmount + $igstAmount + $cessAmount;
        $totalAmount = $taxableAmount + $totalTax;

        return [
            'unit_price' => round($unitPrice, 2),
            'quantity' => round($quantity, 3),
            'discount_percent' => round($discountPct, 2),
            'discount_amount' => round($discountAmount, 2),
            'taxable_amount' => round($taxableAmount, 2),
            'tax_rate' => round($taxRate, 2),
            'cgst_rate' => round($cgstRate, 2),
            'cgst_amount' => round($cgstAmount, 2),
            'sgst_rate' => round($sgstRate, 2),
            'sgst_amount' => round($sgstAmount, 2),
            'igst_rate' => round($igstRate, 2),
            'igst_amount' => round($igstAmount, 2),
            'cess_rate' => round($cessRate, 2),
            'cess_amount' => round($cessAmount, 2),
            'total_tax' => round($totalTax, 2),
            'total_amount' => round($totalAmount, 2)
        ];
    }

    public static function calculateInvoiceTotals(
        array $items,
        string $discountType,
        float $discountValue,
        bool $isInterstate
    ): array {
        $subtotal = 0;
        $taxableAmount = 0;
        $cgstTotal = 0;
        $sgstTotal = 0;
        $igstTotal = 0;
        $cessTotal = 0;
        $taxBreakdown = [];

        foreach ($items as $item) {
            $calc = self::calculateLineItem(
                (float) $item['unit_price'],
                (float) $item['quantity'],
                (float) ($item['discount_percent'] ?? 0),
                (float) ($item['tax_rate'] ?? 0),
                $isInterstate,
                (float) ($item['cess_rate'] ?? 0)
            );

            $subtotal += ($calc['taxable_amount'] + $calc['discount_amount']);
            $taxableAmount += $calc['taxable_amount'];
            $cgstTotal += $calc['cgst_amount'];
            $sgstTotal += $calc['sgst_amount'];
            $igstTotal += $calc['igst_amount'];
            $cessTotal += $calc['cess_amount'];

            $rateKey = (string) $calc['tax_rate'];
            if (!isset($taxBreakdown[$rateKey])) {
                $taxBreakdown[$rateKey] = ['rate' => $calc['tax_rate'], 'taxable' => 0, 'cgst' => 0, 'sgst' => 0, 'igst' => 0];
            }
            $taxBreakdown[$rateKey]['taxable'] += $calc['taxable_amount'];
            $taxBreakdown[$rateKey]['cgst'] += $calc['cgst_amount'];
            $taxBreakdown[$rateKey]['sgst'] += $calc['sgst_amount'];
            $taxBreakdown[$rateKey]['igst'] += $calc['igst_amount'];
        }

        $invoiceDiscountAmount = 0;
        if ($discountType === 'percent') {
            $invoiceDiscountAmount = ($taxableAmount * $discountValue) / 100;
        } else {
            $invoiceDiscountAmount = $discountValue;
        }

        if ($invoiceDiscountAmount > 0 && $taxableAmount > 0) {
            $ratio = ($taxableAmount - $invoiceDiscountAmount) / $taxableAmount;
            $taxableAmount -= $invoiceDiscountAmount;
            $cgstTotal *= $ratio;
            $sgstTotal *= $ratio;
            $igstTotal *= $ratio;
            $cessTotal *= $ratio;
            foreach ($taxBreakdown as &$tb) {
                $tb['taxable'] *= $ratio;
                $tb['cgst'] *= $ratio;
                $tb['sgst'] *= $ratio;
                $tb['igst'] *= $ratio;
            }
        }

        $totalTax = $cgstTotal + $sgstTotal + $igstTotal + $cessTotal;
        $grandTotalBeforeRound = $taxableAmount + $totalTax;
        $grandTotal = round($grandTotalBeforeRound);
        $roundOff = $grandTotal - $grandTotalBeforeRound;

        return [
            'subtotal' => round($subtotal, 2),
            'discount_amount' => round($invoiceDiscountAmount, 2),
            'taxable_amount' => round($taxableAmount, 2),
            'cgst_amount' => round($cgstTotal, 2),
            'sgst_amount' => round($sgstTotal, 2),
            'igst_amount' => round($igstTotal, 2),
            'cess_amount' => round($cessTotal, 2),
            'total_tax' => round($totalTax, 2),
            'grand_total' => round($grandTotal, 2),
            'round_off' => round($roundOff, 2),
            'tax_breakdown' => array_values($taxBreakdown)
        ];
    }

    public static function amountToWords(float $amount): string
    {
        $amount = round($amount, 2);
        $number = explode(".", (string) $amount);
        $rupees = (int) $number[0];
        $paise = isset($number[1]) ? (int) str_pad($number[1], 2, "0", STR_PAD_RIGHT) : 0;
        $res = self::convertNumberToWords($rupees) . " Rupees";
        if ($paise > 0)
            $res .= " and " . self::convertNumberToWords($paise) . " Paise";
        return $res . " Only";
    }

    private static function convertNumberToWords(int $num): string
    {
        $ones = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"];
        $tens = ["", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"];
        if ($num == 0)
            return "Zero";
        if ($num < 20)
            return $ones[$num];
        if ($num < 100)
            return $tens[(int) ($num / 10)] . (($num % 10 !== 0) ? " " . $ones[$num % 10] : "");
        if ($num < 1000)
            return $ones[(int) ($num / 100)] . " Hundred" . (($num % 100 !== 0) ? " " . self::convertNumberToWords($num % 100) : "");
        if ($num < 100000)
            return self::convertNumberToWords((int) ($num / 1000)) . " Thousand" . (($num % 1000 !== 0) ? " " . self::convertNumberToWords($num % 1000) : "");
        if ($num < 10000000)
            return self::convertNumberToWords((int) ($num / 100000)) . " Lakh" . (($num % 100000 !== 0) ? " " . self::convertNumberToWords($num % 100000) : "");
        return self::convertNumberToWords((int) ($num / 10000000)) . " Crore" . (($num % 10000000 !== 0) ? " " . self::convertNumberToWords($num % 10000000) : "");
    }

    /**
     * GSTR-1 Data Retrieval
     */
    public static function getGSTR1Data(int $tenantId, int $month, int $year): array
    {
        $db = Database::getInstance();
        $start = "$year-" . str_pad((string) $month, 2, "0", STR_PAD_LEFT) . "-01";
        $end = date("Y-m-t", strtotime($start));

        // 1. B2B: Registered customers (has GSTIN)
        $b2b = $db->prepare("SELECT i.*, c.gstin as customer_gstin, c.name as customer_name, c.billing_state_code, c.billing_state 
                            FROM invoices i JOIN customers c ON i.customer_id = c.id 
                            WHERE i.tenant_id = ? AND i.invoice_date BETWEEN ? AND ? 
                            AND c.gstin IS NOT NULL AND c.gstin != '' AND i.status != 'cancelled' AND i.invoice_type = 'tax_invoice'");
        $b2b->execute([$tenantId, $start, $end]);
        $b2bData = $b2b->fetchAll();

        // 2. B2C Large: Unregistered, value > 2.5L, Inter-state
        $b2cl = $db->prepare("SELECT i.*, c.name as customer_name, c.billing_state_code 
                             FROM invoices i JOIN customers c ON i.customer_id = c.id 
                             WHERE i.tenant_id = ? AND i.invoice_date BETWEEN ? AND ? 
                             AND (c.gstin IS NULL OR c.gstin = '') AND i.grand_total > 250000 
                             AND i.is_interstate = 1 AND i.status != 'cancelled' AND i.invoice_type = 'tax_invoice'");
        $b2cl->execute([$tenantId, $start, $end]);
        $b2clData = $b2cl->fetchAll();

        // 3. B2C Small: All other unregistered
        $b2cs = $db->prepare("SELECT SUM(ii.taxable_amount) as total_taxable, SUM(ii.cgst_amount + ii.sgst_amount + ii.igst_amount) as total_tax, c.billing_state_code, ii.tax_rate
                             FROM invoices i 
                             JOIN customers c ON i.customer_id = c.id 
                             JOIN invoice_items ii ON i.id = ii.invoice_id
                             WHERE i.tenant_id = ? AND i.invoice_date BETWEEN ? AND ? 
                             AND (c.gstin IS NULL OR c.gstin = '') 
                             AND NOT (i.grand_total > 250000 AND i.is_interstate = 1) 
                             AND i.status != 'cancelled' AND i.invoice_type = 'tax_invoice'
                             GROUP BY c.billing_state_code, ii.tax_rate");
        $b2cs->execute([$tenantId, $start, $end]);
        $b2csData = $b2cs->fetchAll();

        // 4. CDNR: Credit/Debit notes for registered
        $cdnr = $db->prepare("SELECT cn.*, c.gstin as customer_gstin, c.billing_state_code, i.invoice_number 
                             FROM credit_notes cn 
                             JOIN customers c ON cn.customer_id = c.id 
                             LEFT JOIN invoices i ON cn.invoice_id = i.id
                             WHERE cn.tenant_id = ? AND cn.cn_date BETWEEN ? AND ? 
                             AND c.gstin IS NOT NULL AND c.gstin != '' AND cn.status = 'issued'");
        $cdnr->execute([$tenantId, $start, $end]);
        $cdnrData = $cdnr->fetchAll();

        // 5. CDNUR: Credit/Debit notes for unregistered
        $cdnur = $db->prepare("SELECT cn.*, c.billing_state_code 
                              FROM credit_notes cn 
                              JOIN customers c ON cn.customer_id = c.id 
                              WHERE cn.tenant_id = ? AND cn.cn_date BETWEEN ? AND ? 
                              AND (c.gstin IS NULL OR c.gstin = '') AND cn.status = 'issued'");
        $cdnur->execute([$tenantId, $start, $end]);

        return [
            'b2b' => $b2bData,
            'b2cl' => $b2clData,
            'b2cs' => $b2csData,
            'cdnr' => $cdnrData,
            'cdnur' => $cdnur->fetchAll(),
            'hsn' => self::getHSNSummary($tenantId, $start, $end)
        ];
    }

    public static function getGSTR3BData(int $tenantId, int $month, int $year): array
    {
        $db = Database::getInstance();
        $start = "$year-" . str_pad((string) $month, 2, "0", STR_PAD_LEFT) . "-01";
        $end = date("Y-m-t", strtotime($start));

        // 3.1 Outward supplies (Invoices - Sales Returns)
        $outward = $db->prepare("SELECT 
                                (SELECT COALESCE(SUM(taxable_amount),0) FROM invoices WHERE tenant_id = ? AND invoice_date BETWEEN ? AND ? AND status != 'cancelled' AND invoice_type = 'tax_invoice') - 
                                (SELECT COALESCE(SUM(taxable_amount),0) FROM credit_notes WHERE tenant_id = ? AND cn_date BETWEEN ? AND ? AND status = 'issued') as taxable,
                                (SELECT COALESCE(SUM(cgst_amount),0) FROM invoices WHERE tenant_id = ? AND invoice_date BETWEEN ? AND ? AND status != 'cancelled' AND invoice_type = 'tax_invoice') - 
                                (SELECT COALESCE(SUM(cgst_amount),0) FROM credit_notes WHERE tenant_id = ? AND cn_date BETWEEN ? AND ? AND status = 'issued') as cgst,
                                (SELECT COALESCE(SUM(sgst_amount),0) FROM invoices WHERE tenant_id = ? AND invoice_date BETWEEN ? AND ? AND status != 'cancelled' AND invoice_type = 'tax_invoice') - 
                                (SELECT COALESCE(SUM(sgst_amount),0) FROM credit_notes WHERE tenant_id = ? AND cn_date BETWEEN ? AND ? AND status = 'issued') as sgst,
                                (SELECT COALESCE(SUM(igst_amount),0) FROM invoices WHERE tenant_id = ? AND invoice_date BETWEEN ? AND ? AND status != 'cancelled' AND invoice_type = 'tax_invoice') - 
                                (SELECT COALESCE(SUM(igst_amount),0) FROM credit_notes WHERE tenant_id = ? AND cn_date BETWEEN ? AND ? AND status = 'issued') as igst");
        $outward->execute([$tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end, $tenantId, $start, $end]);
        $outwardData = $outward->fetch();

        // 4. Eligible ITC (Purchases - Purchase Returns)
        $itcData = [
            'taxable' => 0 - (float)$db->query("SELECT COALESCE(SUM(taxable_amount),0) FROM debit_notes WHERE tenant_id = $tenantId AND dn_date BETWEEN '$start' AND '$end' AND status = 'issued'")->fetchColumn(),
            'cgst' => 0 - (float)$db->query("SELECT COALESCE(SUM(cgst_amount),0) FROM debit_notes WHERE tenant_id = $tenantId AND dn_date BETWEEN '$start' AND '$end' AND status = 'issued'")->fetchColumn(),
            'sgst' => 0 - (float)$db->query("SELECT COALESCE(SUM(sgst_amount),0) FROM debit_notes WHERE tenant_id = $tenantId AND dn_date BETWEEN '$start' AND '$end' AND status = 'issued'")->fetchColumn(),
            'igst' => 0 - (float)$db->query("SELECT COALESCE(SUM(igst_amount),0) FROM debit_notes WHERE tenant_id = $tenantId AND dn_date BETWEEN '$start' AND '$end' AND status = 'issued'")->fetchColumn(),
        ];

        $totalLiability = (float)($outwardData['cgst'] ?? 0) + (float)($outwardData['sgst'] ?? 0) + (float)($outwardData['igst'] ?? 0);
        $totalITC = (float)($itcData['cgst'] ?? 0) + (float)($itcData['sgst'] ?? 0) + (float)($itcData['igst'] ?? 0);

        return [
            'outward_supplies' => $outwardData,
            'itc_eligible' => $itcData,
            'total_liability' => round($totalLiability, 2),
            'total_itc' => round($totalITC, 2),
            'net_tax_payable' => round(max(0, $totalLiability - $totalITC), 2),
            'tax_payable_breakdown' => [
                'cgst' => max(0, (float)($outwardData['cgst'] ?? 0) - (float)($itcData['cgst'] ?? 0)),
                'sgst' => max(0, (float)($outwardData['sgst'] ?? 0) - (float)($itcData['sgst'] ?? 0)),
                'igst' => max(0, (float)($outwardData['igst'] ?? 0) - (float)($itcData['igst'] ?? 0))
            ]
        ];
    }

    public static function getHSNSummary(int $tenantId, string $fromDate, string $toDate): array
    {
        $db = Database::getInstance();
        $sql = "SELECT ii.hsn_code, ii.description, ii.unit, SUM(ii.quantity) as total_qty, 
                SUM(ii.taxable_amount) as total_taxable, 
                SUM(ii.cgst_amount) as cgst, SUM(ii.sgst_amount) as sgst, SUM(ii.igst_amount) as igst
                FROM invoice_items ii 
                JOIN invoices i ON ii.invoice_id = i.id
                WHERE i.tenant_id = ? AND i.invoice_date BETWEEN ? AND ? AND i.status != 'cancelled'
                GROUP BY ii.hsn_code, ii.description, ii.unit";
        $stmt = $db->prepare($sql);
        $stmt->execute([$tenantId, $fromDate, $toDate]);
        return $stmt->fetchAll();
    }

    public static function getITCLedger(int $tenantId, string $fromDate, string $toDate): array
    {
        return [];
    }

    public static function exportGSTR1JSON(int $tenantId, int $month, int $year, string $tenantGstin): string
    {
        $data = self::getGSTR1Data($tenantId, $month, $year);
        
        $gstn = [
            'gstin' => $tenantGstin,
            'fp' => str_pad((string)$month, 2, "0", STR_PAD_LEFT) . $year,
            'gt' => 0.0,
            'cur_gt' => 0.0,
            'b2b' => [],
            'b2cl' => [],
            'b2cs' => [],
            'cdnr' => [],
            'cdnur' => [],
            'hsn' => ['data' => []],
            'doc_det' => ['doc_num' => 1, 'docs' => []]
        ];

        // Group B2B by Customer GSTIN
        $b2bGroup = [];
        foreach($data['b2b'] as $inv) {
            $ctin = $inv['customer_gstin'];
            if(!isset($b2bGroup[$ctin])) $b2bGroup[$ctin] = ['ctin' => $ctin, 'inv' => []];
            
            $b2bGroup[$ctin]['inv'][] = [
                'inum' => $inv['invoice_number'],
                'idt' => date('d-m-Y', strtotime($inv['invoice_date'])),
                'val' => (float)$inv['grand_total'],
                'pos' => $inv['billing_state_code'],
                'rchrg' => 'N',
                'inv_typ' => 'R',
                'itms' => [[
                    'num' => 1,
                    'itm_det' => [
                        'rt' => (float)$inv['tax_rate'],
                        'txval' => (float)$inv['taxable_amount'],
                        'iamt' => (float)$inv['igst_amount'],
                        'camt' => (float)$inv['cgst_amount'],
                        'samt' => (float)$inv['sgst_amount'],
                        'csamt' => 0.0
                    ]
                ]]
            ];
        }
        $gstn['b2b'] = array_values($b2bGroup);

        // HSN Summary
        $num = 1;
        foreach($data['hsn'] as $hsn) {
            $gstn['hsn']['data'][] = [
                'num' => $num++,
                'hsn_sc' => $hsn['hsn_code'],
                'desc' => $hsn['description'],
                'uqc' => $hsn['unit'] ?: 'NOS',
                'qty' => (float)$hsn['total_qty'],
                'val' => (float)($hsn['total_taxable'] + $hsn['cgst'] + $hsn['sgst'] + $hsn['igst']),
                'txval' => (float)$hsn['total_taxable'],
                'iamt' => (float)$hsn['igst'],
                'camt' => (float)$hsn['cgst'],
                'samt' => (float)$hsn['sgst'],
                'csamt' => 0.0
            ];
        }

        $gstn['hsn']['data'] = array_values($gstn['hsn']['data']);

        // CDNUR (Unregistered)
        foreach($data['cdnur'] as $cn) {
            $gstn['cdnur'][] = [
                'nt_num' => $cn['cn_number'],
                'nt_dt' => date('d-m-Y', strtotime($cn['cn_date'])),
                'nt_typ' => 'C',
                'val' => (float)$cn['grand_total'],
                'pos' => $cn['billing_state_code'],
                'itms' => [[
                    'num' => 1,
                    'itm_det' => [
                        'rt' => (float)$cn['tax_rate'],
                        'txval' => (float)$cn['taxable_amount']
                    ]
                ]]
            ];
        }

        return json_encode($gstn, JSON_PRETTY_PRINT);
    }

    public static function exportGSTR3BJSON(int $tenantId, int $month, int $year, string $tenantGstin): string
    {
        $data = self::getGSTR3BData($tenantId, $month, $year);
        $fp = str_pad((string)$month, 2, "0", STR_PAD_LEFT) . $year;
        
        $json = [
            'gstin' => $tenantGstin,
            'fp' => $fp,
            'sup_details' => [
                'osup_det' => [
                    'txval' => (float)$data['outward_supplies']['taxable'],
                    'iamt' => (float)$data['outward_supplies']['igst'],
                    'camt' => (float)$data['outward_supplies']['cgst'],
                    'samt' => (float)$data['outward_supplies']['sgst'],
                    'csamt' => 0.0
                ],
                'isup_rev' => ['txval' => 0.0, 'iamt' => 0.0, 'camt' => 0.0, 'samt' => 0.0, 'csamt' => 0.0]
            ],
            'itc_elg' => [
                'itc_avl' => [[
                    'ty' => 'OTH',
                    'iamt' => (float)$data['itc_eligible']['igst'],
                    'camt' => (float)$data['itc_eligible']['cgst'],
                    'samt' => (float)$data['itc_eligible']['sgst'],
                    'csamt' => 0.0
                ]]
            ]
        ];

        return json_encode($json, JSON_PRETTY_PRINT);
    }
}
