<?php

namespace App\Services;

use App\Models\PurchaseOrder;
use App\Models\GatePass;
use App\Models\Qc;
use App\Models\Grn;
use App\Models\Account;
use Carbon\Carbon;

class NumberGenerationService
{
    /**
     * Generate a unique number for any module
     *
     * @param string $module The module type (po, gp, qc, grn)
     * @return string
     */
    public function generateNumber(string $module): string
    {
        $currentYear = Carbon::now()->format('Y');
        $currentMonth = Carbon::now()->format('m');

        $prefix = $this->getModulePrefix($module);
        $table = $this->getModuleTable($module);
        $numberField = $this->getModuleNumberField($module);

        // Get the last number for current year/month
        $lastRecord = \DB::table($table)
            ->where($numberField, 'like', "{$prefix}-{$currentYear}{$currentMonth}-%")
            ->orderBy($numberField, 'desc')
            ->first();

        if ($lastRecord) {
            // Extract the sequence number and increment it
            $parts = explode('-', $lastRecord->$numberField);
            $sequence = (int) end($parts);
            $newSequence = $sequence + 1;
        } else {
            // First record for this year/month
            $newSequence = 1;
        }

        // Format: PREFIX-YYYYMM-001
        return sprintf("%s-%s%s-%03d", $prefix, $currentYear, $currentMonth, $newSequence);
    }

    /**
     * Check if a number already exists
     *
     * @param string $module The module type
     * @param string $number The number to check
     * @param int|null $excludeId ID to exclude from check
     * @return bool
     */
    public function isNumberExists(string $module, string $number, ?int $excludeId = null): bool
    {
        $table = $this->getModuleTable($module);
        $numberField = $this->getModuleNumberField($module);
        $idField = $this->getModuleIdField($module);

        $query = \DB::table($table)->where($numberField, $number);

        if ($excludeId) {
            $query->where($idField, '!=', $excludeId);
        }

        return $query->exists();
    }

    /**
     * Validate and generate number if needed
     *
     * @param string $module The module type
     * @param string|null $number The number to validate
     * @param int|null $excludeId ID to exclude from check
     * @return string
     */
    public function validateAndGenerateNumber(string $module, ?string $number, ?int $excludeId = null): string
    {
        // If no number provided or empty, generate one
        if (empty($number)) {
            return $this->generateNumber($module);
        }

        // If number provided, check if it's unique
        if ($this->isNumberExists($module, $number, $excludeId)) {
            // If not unique, generate a new one
            return $this->generateNumber($module);
        }

        return $number;
    }

    /**
     * Get module prefix for number generation
     *
     * @param string $module
     * @return string
     */
        private function getModulePrefix(string $module): string
    {
        $prefixes = [
            'po' => 'PO',
            'gp' => 'GP',
            'ogp' => 'OGP',
            'qc' => 'QC',
            'grn' => 'GRN',
            'pr' => 'PR',
            'manufacturing' => 'M',
            'transaction' => 'TXN',
        ];

        return $prefixes[$module] ?? 'DOC';
    }

    /**
     * Get module table name
     *
     * @param string $module
     * @return string
     */
        private function getModuleTable(string $module): string
    {
        $tables = [
            'po' => 'purchase_orders',
            'gp' => 'gate_passes',
            'ogp' => 'gate_passes',
            'qc' => 'qcs',
            'grn' => 'good_received_notes',
            'pr' => 'purchase_requests',
            'manufacturing' => 'manufacturing_runs',
            'transaction' => 'account_transactions',
        ];

        return $tables[$module] ?? 'documents';
    }

    /**
     * Get module number field name
     *
     * @param string $module
     * @return string
     */
        private function getModuleNumberField(string $module): string
    {
        $fields = [
            'po' => 'po_number',
            'gp' => 'gate_pass_number',
            'ogp' => 'gate_pass_number',
            'qc' => 'qc_number',
            'grn' => 'grn_number',
            'pr' => 'pr_number',
            'manufacturing' => 'm_number',
            'transaction' => 'transaction_number',
        ];

        return $fields[$module] ?? 'number';
    }

    /**
     * Get module ID field name
     *
     * @param string $module
     * @return string
     */
        private function getModuleIdField(string $module): string
    {
        $fields = [
            'po' => 'id',
            'gp' => 'id',
            'qc' => 'id',
            'grn' => 'id',
            'pr' => 'id',
        ];

        return $fields[$module] ?? 'id';
    }

    /**
     * Generate PO number
     *
     * @return string
     */
    public function generatePONumber(): string
    {
        return $this->generateNumber('po');
    }

    /**
     * Generate Gate Pass number
     *
     * @return string
     */
    public function generateGPNumber(): string
    {
        return $this->generateNumber('gp');
    }

    /**
     * Generate QC number
     *
     * @return string
     */
    public function generateQCNumber(): string
    {
        return $this->generateNumber('qc');
    }

    /**
     * Generate GRN number
     *
     * @return string
     */
    public function generateGRNNumber(): string
    {
        return $this->generateNumber('grn');
    }

    /**
     * Generate PR number
     *
     * @return string
     */
    public function generatePRNumber(): string
    {
        return $this->generateNumber('pr');
    }

    /**
     * Generate Manufacturing number
     *
     * @return string
     */
    public function generateManufacturingNumber(): string
    {
        return $this->generateNumber('manufacturing');
    }

    /**
     * Generate Transaction number
     *
     * @return string
     */
    public function generateTransactionNumber(): string
    {
        return $this->generateNumber('transaction');
    }

    /**
     * Generate Account Code for any account type
     *
     * @param string $chartCode
     * @param string $accountName
     * @param string $modelType
     * @return string
     */
    public function generateAccountCode(string $chartCode, string $accountName, string $modelType = 'account'): string
    {
        // Create a base code using chart code + sanitized account name
        $sanitizedName = preg_replace('/[^A-Za-z0-9]/', '', $accountName);
        $baseCode = $chartCode . '-' . strtoupper(substr($sanitizedName, 0, 6));

        // Ensure uniqueness by appending a number if needed
        $counter = 1;
        $finalCode = $baseCode;

        while (Account::where('code', $finalCode)->exists()) {
            $finalCode = $baseCode . '-' . str_pad($counter, 3, '0', STR_PAD_LEFT);
            $counter++;
        }

        return $finalCode;
    }

    /**
     * Generate Supplier Code
     *
     * @param string $supplierName
     * @return string
     */
    public function generateSupplierCode(string $supplierName): string
    {
        // Generate supplier code: SUP-XXXX format
        $baseCode = 'SUP';

        // Get the highest existing supplier code number
        $lastSupplier = Account::where('model_type', 'supplier')
            ->whereNotNull('supplier_code')
            ->where('supplier_code', 'like', 'SUP-%')
            ->orderByRaw('CAST(SUBSTRING(supplier_code, 5) AS UNSIGNED) DESC')
            ->first();

        $counter = 1;
        if ($lastSupplier && $lastSupplier->supplier_code) {
            // Extract number from last supplier code
            $lastCode = $lastSupplier->supplier_code;
            if (preg_match('/SUP-(\d+)/', $lastCode, $matches)) {
                $counter = (int)$matches[1] + 1;
            }
        }

        $code = $baseCode . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);

        // Double-check for uniqueness
        while (Account::where('supplier_code', $code)->exists()) {
            $counter++;
            $code = $baseCode . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);
        }

        return $code;
    }

    /**
     * Generate Customer Code
     *
     * @param string $customerName
     * @return string
     */
    public function generateCustomerCode(string $customerName): string
    {
        // Generate customer code: CUST-XXXX format
        $baseCode = 'CUST';

        // Get the highest existing customer code number
        $lastCustomer = Account::where('model_type', 'customer')
            ->whereNotNull('code')
            ->where('code', 'like', 'CUST-%')
            ->orderBy('code', 'desc')
            ->first();

        $counter = 1;
        if ($lastCustomer && $lastCustomer->code) {
            // Extract number from last customer code
            $lastCode = $lastCustomer->code;
            if (preg_match('/CUST-(\d+)/', $lastCode, $matches)) {
                $counter = (int)$matches[1] + 1;
            }
        }

        $code = $baseCode . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);

        // Double-check for uniqueness
        while (Account::where('code', $code)->exists()) {
            $counter++;
            $code = $baseCode . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);
        }

        return $code;
    }

    /**
     * Generate Secure SKU for Items
     *
     * @param string $type
     * @param string $itemName
     * @return string
     */
    public function generateSecureSKU(string $type, string $itemName): string
    {
        // Use timestamp, microtime, and random bytes for absolute uniqueness
        $timestamp = now()->format('YmdHis');
        $microtime = str_replace('.', '', microtime(true));
        $randomBytes = bin2hex(random_bytes(4)); // 8 characters

        // Create a hash-based identifier from item name for consistency
        $nameHash = substr(hash('sha256', strtolower(trim($itemName))), 0, 6);

        // Combine all elements to create an ultra-secure SKU
        $secureSku = $type . '-' . $timestamp . '-' . $nameHash . '-' . $randomBytes;

        // Ensure absolute uniqueness in database
        $counter = 1;
        $originalSku = $secureSku;

        while (Account::where('sku', $secureSku)->exists()) {
            $secureSku = $originalSku . '-' . str_pad($counter, 3, '0', STR_PAD_LEFT);
            $counter++;
        }

        return $secureSku;
    }

    /**
     * Generate GRN Number
     *
     * @return string
     */
    // public function generateGRNNumber(): string
    // {
    //     return $this->generateNumber('grn');
    // }

    /**
     * Generate Unique SKU for Items
     *
     * @param string $type (RM or FG)
     * @return string
     */
    public function generateUniqueSKU(string $type): string
    {
        // Get the highest existing SKU number for the given type with 4-digit format
        // Exclude soft-deleted items and shop items
        $lastItem = Account::where('model_type', 'Item')
            ->where('is_shop_item', 0) // Exclude shop items
            ->whereNull('deleted_at') // Exclude soft-deleted items
            ->whereNotNull('sku')
            ->where('sku', 'like', $type . '-%')
            ->where('sku', 'regexp', '^' . $type . '-[0-9]{4}$') // Only 4-digit format
            ->orderByRaw('CAST(SUBSTRING(sku, 4) AS UNSIGNED) DESC')
            ->first();

        $counter = 1;
        if ($lastItem && $lastItem->sku) {
            // Extract number from last SKU
            $lastSku = $lastItem->sku;
            if (preg_match('/' . $type . '-(\d{4})/', $lastSku, $matches)) {
                $counter = (int)$matches[1] + 1;
            }
        }

        $sku = $type . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);

        // Double-check for uniqueness (excluding shop items and soft-deleted items)
        while (Account::where('sku', $sku)
            ->where('is_shop_item', 0)
            ->whereNull('deleted_at') // Exclude soft-deleted items
            ->exists()) {
            $counter++;
            $sku = $type . '-' . str_pad($counter, 4, '0', STR_PAD_LEFT);
        }

        return $sku;
    }

    /**
     * Generate Unique Barcode for Items
     *
     * @return string
     */
    public function generateUniqueBarcode(): string
    {
        // Generate barcode: 13-digit EAN-13 format (starting with 2 for internal use)
        // Format: 2XXXXXXXXXXX (13 digits total)

        // Get the highest existing barcode number
        $lastItem = Account::where('model_type', 'Item')
            ->where('is_shop_item', 0) // Exclude shop items
            ->whereNotNull('barcode')
            ->where('barcode', 'like', '2%')
            ->whereRaw('LENGTH(barcode) = 13')
            ->orderByRaw('CAST(SUBSTRING(barcode, 2) AS UNSIGNED) DESC')
            ->first();

        $counter = 1;
        if ($lastItem && $lastItem->barcode) {
            // Extract number from last barcode (skip first digit '2')
            $lastBarcode = $lastItem->barcode;
            $numberPart = substr($lastBarcode, 1); // Remove first '2'
            if (is_numeric($numberPart)) {
                $counter = (int)$numberPart + 1;
            }
        }

        // Generate 13-digit barcode: 2 + 12-digit number
        $barcode = '2' . str_pad($counter, 12, '0', STR_PAD_LEFT);

        // Double-check for uniqueness (excluding shop items)
        while (Account::where('barcode', $barcode)->where('is_shop_item', 0)->exists()) {
            $counter++;
            $barcode = '2' . str_pad($counter, 12, '0', STR_PAD_LEFT);
        }

        return $barcode;
    }

    /**
     * Generate Unique Supplier Code
     *
     * @return string
     */
    public function generateUniqueSupplier(): string
    {
        // Get the highest existing supplier code number
        $lastSupplier = Account::where('model_type', 'supplier')
            ->whereNotNull('supplier_code')
            ->where('supplier_code', 'like', 'SUP-%')
            ->orderByRaw('CAST(SUBSTRING(supplier_code, 5) AS UNSIGNED) DESC')
            ->first();

        $counter = 1;
        if ($lastSupplier && $lastSupplier->supplier_code) {
            // Extract number from last supplier code
            $lastCode = $lastSupplier->supplier_code;
            if (preg_match('/SUP-(\d+)/', $lastCode, $matches)) {
                $counter = (int)$matches[1] + 1;
            }
        }

        $supplierCode = 'SUP-' . str_pad($counter, 4, '0', STR_PAD_LEFT);

        // Double-check for uniqueness
        while (Account::where('supplier_code', $supplierCode)->exists()) {
            $counter++;
            $supplierCode = 'SUP-' . str_pad($counter, 4, '0', STR_PAD_LEFT);
        }

        return $supplierCode;
    }
}
