<?php
declare(strict_types=1);

namespace App\Helpers;

use App\Config\Database;
use DateTime;
use Exception;

class InvoiceHelper
{
    public static function getFinancialYear(string $date): string
    {
        $dt = new DateTime($date);
        $month = (int)$dt->format('n');
        $year = (int)$dt->format('Y');

        if ($month >= 4) {
            $nextYear = substr((string)($year + 1), -2);
            return $year . '-' . $nextYear;
        } else {
            $prevYear = substr((string)$year, -2);
            return ($year - 1) . '-' . $prevYear;
        }
    }

    public static function generateInvoiceNumber(int $tenantId): string
    {
        $db = Database::getInstance();
        $date = date('Y-m-d');
        $fy = self::getFinancialYear($date);
        
        $hasTransaction = $db->inTransaction();
        if (!$hasTransaction) {
            $db->beginTransaction();
        }
        
        try {
            // Get current count for this FY
            $stmt = $db->prepare("SELECT COUNT(*) FROM invoices WHERE tenant_id = ? AND invoice_number LIKE ? FOR UPDATE");
            $likePattern = "%/{$fy}/%";
            $stmt->execute([$tenantId, $likePattern]);
            $count = (int)$stmt->fetchColumn();
            
            $nextNum = str_pad((string)($count + 1), 4, "0", STR_PAD_LEFT);
            $invNumber = "INV/{$fy}/{$nextNum}";
            
            if (!$hasTransaction) {
                $db->commit();
            }
            
            return $invNumber;
        } catch (Exception $e) {
            if (!$hasTransaction) {
                $db->rollBack();
            }
            throw new Exception("Failed to generate invoice number: " . $e->getMessage());
        }
    }

    public static function generateAutoNumber(string $table, string $prefix, int $tenantId): string
    {
        $db = Database::getInstance();
        $date = date('Y-m-d');
        $fy = self::getFinancialYear($date);
        
        $hasTransaction = $db->inTransaction();
        if (!$hasTransaction) {
            $db->beginTransaction();
        }
        
        try {
            $col = match($table) {
                'credit_notes' => 'cn_number',
                'debit_notes' => 'dn_number',
                'expenses' => 'expense_number',
                default => 'invoice_number'
            };

            $stmt = $db->prepare("SELECT COUNT(*) FROM {$table} WHERE tenant_id = ? AND {$col} LIKE ? FOR UPDATE");
            $likePattern = "%/{$fy}/%";
            $stmt->execute([$tenantId, $likePattern]);
            $count = (int)$stmt->fetchColumn();
            
            $nextNum = str_pad((string)($count + 1), 4, "0", STR_PAD_LEFT);
            $number = "{$prefix}/{$fy}/{$nextNum}";
            
            if (!$hasTransaction) {
                $db->commit();
            }
            
            return $number;
        } catch (Exception $e) {
            if (!$hasTransaction) {
                $db->rollBack();
            }
            throw new Exception("Failed to generate number: " . $e->getMessage());
        }
    }

    public static function isOverdue(array $invoice): bool
    {
        if (empty($invoice['due_date'])) {
            return false;
        }
        
        if (in_array($invoice['status'], ['paid', 'cancelled'])) {
            return false;
        }
        
        $today = new DateTime('today');
        $dueDate = new DateTime($invoice['due_date']);
        
        return $today > $dueDate && $invoice['balance_due'] > 0;
    }

    public static function calculateDueDate(string $invoiceDate, string $paymentTerms): string
    {
        $dt = new DateTime($invoiceDate);
        
        if (preg_match('/due on receipt/i', $paymentTerms)) {
            return $dt->format('Y-m-d');
        }
        
        if (preg_match('/(\d+)\s+days/i', $paymentTerms, $matches)) {
            $days = (int)$matches[1];
            $dt->modify("+{$days} days");
            return $dt->format('Y-m-d');
        }
        
        // Default to Net 30 if no match
        $dt->modify('+30 days');
        return $dt->format('Y-m-d');
    }

    public static function amountToWords(float $number): string
    {
        $hyphen      = '-';
        $conjunction = ' and ';
        $separator   = ', ';
        $negative    = 'negative ';
        $decimal     = ' point ';
        $dictionary  = array(
            0                   => 'zero',
            1                   => 'one',
            2                   => 'two',
            3                   => 'three',
            4                   => 'four',
            5                   => 'five',
            6                   => 'six',
            7                   => 'seven',
            8                   => 'eight',
            9                   => 'nine',
            10                  => 'ten',
            11                  => 'eleven',
            12                  => 'twelve',
            13                  => 'thirteen',
            14                  => 'fourteen',
            15                  => 'fifteen',
            16                  => 'sixteen',
            17                  => 'seventeen',
            18                  => 'eighteen',
            19                  => 'nineteen',
            20                  => 'twenty',
            30                  => 'thirty',
            40                  => 'forty',
            50                  => 'fifty',
            60                  => 'sixty',
            70                  => 'seventy',
            80                  => 'eighty',
            90                  => 'ninety',
            100                 => 'hundred',
            1000                => 'thousand',
            1000000             => 'million',
            1000000000          => 'billion',
            1000000000000       => 'trillion',
            1000000000000000    => 'quadrillion',
            1000000000000000000 => 'quintillion'
        );

        if (!is_numeric($number)) {
            return '';
        }

        if (($number >= 0 && (int) $number < 0) || (int) $number < 0 - PHP_INT_MAX) {
            trigger_error(
                'amountToWords only accepts numbers between -' . PHP_INT_MAX . ' and ' . PHP_INT_MAX,
                E_USER_WARNING
            );
            return '';
        }

        if ($number < 0) {
            return $negative . self::amountToWords(abs($number));
        }

        $string = $fraction = null;

        if (strpos((string)$number, '.') !== false) {
            list($number, $fraction) = explode('.', (string)$number);
        }

        switch (true) {
            case $number < 21:
                $string = $dictionary[$number];
                break;
            case $number < 100:
                $tens   = ((int) ($number / 10)) * 10;
                $units  = $number % 10;
                $string = $dictionary[$tens];
                if ($units) {
                    $string .= $hyphen . $dictionary[$units];
                }
                break;
            case $number < 1000:
                $hundreds  = $number / 100;
                $remainder = $number % 100;
                $string = $dictionary[(int) $hundreds] . ' ' . $dictionary[100];
                if ($remainder) {
                    $string .= $conjunction . self::amountToWords($remainder);
                }
                break;
            default:
                $baseUnit     = pow(1000, floor(log($number, 1000)));
                $numBaseUnits = (int) ($number / $baseUnit);
                $remainder    = $number % $baseUnit;
                $string       = self::amountToWords($numBaseUnits) . ' ' . $dictionary[$baseUnit];
                if ($remainder) {
                    $string .= $remainder < 100 ? $conjunction : $separator;
                    $string .= self::amountToWords($remainder);
                }
                break;
        }

        if (null !== $fraction && is_numeric($fraction)) {
            $string .= $decimal;
            $words = array();
            foreach (str_split((string) $fraction) as $number) {
                $words[] = $dictionary[$number];
            }
            $string .= implode(' ', $words);
        }

        return $string;
    }
}
