<?php

namespace App\Helpers;

use App\Models\Inquiry;
use Illuminate\Support\Str;

/**
 * Reference Number Generator
 * 
 * Generates unique reference numbers for insurance inquiries and other entities.
 * Provides various formats and validation for reference numbers across the platform.
 */
class ReferenceNumberGenerator
{
    /**
     * Generate a unique reference number for an inquiry
     * 
     * Format: INQ-XXXXXXX (e.g., INQ-A1B2C3D4)
     * 
     * @param string|null $prefix Custom prefix (default: 'INQ')
     * @return string Generated reference number
     */
    public static function generateInquiryReference(?string $prefix = null): string
    {
        $prefix = $prefix ?? 'INQ';
        
        do {
            $reference = $prefix . '-' . strtoupper(Str::random(8));
        } while (self::referenceExists($reference, 'inquiries'));
        
        return $reference;
    }
    
    /**
     * Generate a reference number for a payment
     * 
     * Format: PAY-YYYYMMDD-XXXXXX (e.g., PAY-20231225-A1B2C3)
     * 
     * @return string Generated payment reference
     */
    public static function generatePaymentReference(): string
    {
        $date = date('Ymd');
        
        do {
            $reference = 'PAY-' . $date . '-' . strtoupper(Str::random(6));
        } while (self::referenceExists($reference, 'payments'));
        
        return $reference;
    }
    
    /**
     * Generate a reference number for a policy
     * 
     * Format: POL-XX-YYYY-XXXXXX (e.g., POL-CA-2023-A1B2C3)
     * 
     * @param string $countryCode Country code (e.g., 'CA' for Canada)
     * @return string Generated policy reference
     */
    public static function generatePolicyReference(string $countryCode = 'CA'): string
    {
        $year = date('Y');
        
        do {
            $reference = 'POL-' . strtoupper($countryCode) . '-' . $year . '-' . strtoupper(Str::random(6));
        } while (self::referenceExists($reference, 'policies'));
        
        return $reference;
    }
    
    /**
     * Generate a reference number for an agent
     * 
     * Format: AGT-XXX-XXXX (e.g., AGT-001-A1B2)
     * 
     * @param int $agentId Agent ID (for sequential part)
     * @return string Generated agent reference
     */
    public static function generateAgentReference(int $agentId): string
    {
        $sequential = str_pad($agentId, 3, '0', STR_PAD_LEFT);
        
        do {
            $reference = 'AGT-' . $sequential . '-' . strtoupper(Str::random(4));
        } while (self::referenceExists($reference, 'agents', 'agent_code'));
        
        return $reference;
    }
    
    /**
     * Generate a reference number for a questionnaire
     * 
     * Format: QST-XXXXXX-XX (e.g., QST-A1B2C3-01)
     * 
     * @param int $inquiryId Related inquiry ID
     * @return string Generated questionnaire reference
     */
    public static function generateQuestionnaireReference(int $inquiryId): string
    {
        $inquiryPart = substr(strtoupper(Str::random(6)), 0, 6);
        $sequence = str_pad($inquiryId % 100, 2, '0', STR_PAD_LEFT);
        
        do {
            $reference = 'QST-' . $inquiryPart . '-' . $sequence;
        } while (self::referenceExists($reference, 'questionnaires'));
        
        return $reference;
    }
    
    /**
     * Check if a reference number already exists in a table
     * 
     * @param string $reference Reference number to check
     * @param string $table Database table name
     * @param string $column Column name (default: 'reference_number')
     * @return bool True if reference exists
     */
    public static function referenceExists(string $reference, string $table, string $column = 'reference_number'): bool
    {
        return \DB::table($table)->where($column, $reference)->exists();
    }
    
    /**
     * Validate a reference number format
     * 
     * @param string $reference Reference number to validate
     * @param string $type Type of reference (inquiry, payment, policy, agent, questionnaire)
     * @return bool True if valid format
     */
    public static function validateReferenceFormat(string $reference, string $type = 'inquiry'): bool
    {
        $patterns = [
            'inquiry' => '/^INQ-[A-Z0-9]{8}$/',
            'payment' => '/^PAY-\d{8}-[A-Z0-9]{6}$/',
            'policy' => '/^POL-[A-Z]{2}-\d{4}-[A-Z0-9]{6}$/',
            'agent' => '/^AGT-\d{3}-[A-Z0-9]{4}$/',
            'questionnaire' => '/^QST-[A-Z0-9]{6}-\d{2}$/',
        ];
        
        $pattern = $patterns[$type] ?? $patterns['inquiry'];
        return preg_match($pattern, $reference) === 1;
    }
    
    /**
     * Extract information from a reference number
     * 
     * @param string $reference Reference number to parse
     * @return array|null Parsed information or null if invalid
     */
    public static function parseReference(string $reference): ?array
    {
        if (!self::validateReferenceFormat($reference)) {
            return null;
        }
        
        $parts = explode('-', $reference);
        $prefix = $parts[0];
        
        $info = [
            'prefix' => $prefix,
            'type' => self::getTypeFromPrefix($prefix),
            'full_reference' => $reference,
        ];
        
        switch ($prefix) {
            case 'INQ':
                $info['unique_code'] = $parts[1];
                break;
                
            case 'PAY':
                $info['date'] = $parts[1];
                $info['unique_code'] = $parts[2];
                $info['parsed_date'] = \Carbon\Carbon::createFromFormat('Ymd', $parts[1]);
                break;
                
            case 'POL':
                $info['country_code'] = $parts[1];
                $info['year'] = $parts[2];
                $info['unique_code'] = $parts[3];
                break;
                
            case 'AGT':
                $info['agent_number'] = (int) $parts[1];
                $info['unique_code'] = $parts[2];
                break;
                
            case 'QST':
                $info['unique_code'] = $parts[1];
                $info['sequence'] = (int) $parts[2];
                break;
        }
        
        return $info;
    }
    
    /**
     * Get entity type from reference prefix
     * 
     * @param string $prefix Reference prefix
     * @return string Entity type
     */
    private static function getTypeFromPrefix(string $prefix): string
    {
        return match($prefix) {
            'INQ' => 'inquiry',
            'PAY' => 'payment',
            'POL' => 'policy',
            'AGT' => 'agent',
            'QST' => 'questionnaire',
            default => 'unknown',
        };
    }
    
    /**
     * Generate a batch of unique reference numbers
     * 
     * @param string $type Type of reference
     * @param int $count Number of references to generate
     * @param array $options Generation options
     * @return array Array of generated references
     */
    public static function generateBatch(string $type = 'inquiry', int $count = 10, array $options = []): array
    {
        $references = [];
        
        for ($i = 0; $i < $count; $i++) {
            $reference = match($type) {
                'payment' => self::generatePaymentReference(),
                'policy' => self::generatePolicyReference($options['country_code'] ?? 'CA'),
                'agent' => self::generateAgentReference($options['agent_id'] ?? ($i + 1)),
                'questionnaire' => self::generateQuestionnaireReference($options['inquiry_id'] ?? ($i + 1)),
                default => self::generateInquiryReference($options['prefix'] ?? null),
            };
            
            $references[] = $reference;
        }
        
        return $references;
    }
    
    /**
     * Format a reference number for display
     * 
     * @param string $reference Reference number
     * @param bool $withSpaces Add spaces for readability
     * @return string Formatted reference
     */
    public static function formatForDisplay(string $reference, bool $withSpaces = true): string
    {
        if (!$withSpaces) {
            return $reference;
        }
        
        // Add spaces after dashes for better readability
        return str_replace('-', '- ', $reference);
    }
    
    /**
     * Get the next sequential reference number
     * 
     * @param string $type Type of reference
     * @param string $prefix Custom prefix
     * @return string Next sequential reference
     */
    public static function getNextSequential(string $type = 'inquiry', string $prefix = null): string
    {
        $table = match($type) {
            'payment' => 'payments',
            'policy' => 'policies',
            'agent' => 'agents',
            'questionnaire' => 'questionnaires',
            default => 'inquiries',
        };
        
        $column = match($type) {
            'agent' => 'agent_code',
            default => 'reference_number',
        };
        
        $prefix = $prefix ?? match($type) {
            'payment' => 'PAY',
            'policy' => 'POL',
            'agent' => 'AGT',
            'questionnaire' => 'QST',
            default => 'INQ',
        };
        
        // Get the highest sequential number
        $lastReference = \DB::table($table)
            ->where($column, 'like', $prefix . '-%')
            ->orderByDesc($column)
            ->value($column);
        
        if (!$lastReference) {
            $sequential = 1;
        } else {
            // Extract number from reference (assuming format: PREFIX-NUMBER-...)
            $parts = explode('-', $lastReference);
            $lastNumber = (int) $parts[1];
            $sequential = $lastNumber + 1;
        }
        
        // Format with leading zeros
        $sequentialFormatted = str_pad($sequential, 6, '0', STR_PAD_LEFT);
        
        do {
            $reference = $prefix . '-' . $sequentialFormatted . '-' . strtoupper(Str::random(4));
            $sequential++;
            $sequentialFormatted = str_pad($sequential, 6, '0', STR_PAD_LEFT);
        } while (self::referenceExists($reference, $table, $column));
        
        return $reference;
    }
    
    /**
     * Generate a human-readable reference with date
     * 
     * Format: INQ-YYYYMMDD-XXX-XXX (e.g., INQ-20231225-ABC-123)
     * 
     * @return string Human-readable reference
     */
    public static function generateHumanReadableReference(): string
    {
        $date = date('Ymd');
        $random1 = strtoupper(Str::random(3));
        $random2 = strtoupper(Str::random(3));
        
        do {
            $reference = 'INQ-' . $date . '-' . $random1 . '-' . $random2;
            $random1 = strtoupper(Str::random(3));
            $random2 = strtoupper(Str::random(3));
        } while (self::referenceExists($reference, 'inquiries'));
        
        return $reference;
    }
    
    /**
     * Generate a short reference number (for URLs, etc.)
     * 
     * @param int $length Length of reference (default: 8)
     * @return string Short reference
     */
    public static function generateShortReference(int $length = 8): string
    {
        // Use alphanumeric characters, avoiding similar ones (0/O, 1/I/l)
        $characters = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
        
        do {
            $reference = '';
            for ($i = 0; $i < $length; $i++) {
                $reference .= $characters[rand(0, strlen($characters) - 1)];
            }
        } while (self::referenceExists($reference, 'inquiries', 'reference_number'));
        
        return $reference;
    }
}