<?php

namespace App\Services;

use App\Models\NumberSequence;
use Carbon\Carbon;

class NumberGeneratorService
{
    // Number format templates
    const FORMATS = [
        'invoice' => [
            'prefix' => 'INV',
            'pattern' => '{prefix}-{date}-{sequence}',
            'sequence_name' => 'invoice',
            'example' => 'INV-20231201-001'
        ],
        'receipt' => [
            'prefix' => 'RCPT',
            'pattern' => '{prefix}-{date}-{sequence}',
            'sequence_name' => 'receipt',
            'example' => 'RCPT-20231201-001'
        ],
        'credit_note' => [
            'prefix' => 'CN',
            'pattern' => '{prefix}-{date}-{sequence}',
            'sequence_name' => 'credit_note',
            'example' => 'CN-20231201-001'
        ],
        'refund' => [
            'prefix' => 'REF',
            'pattern' => '{prefix}-{date}-{sequence}',
            'sequence_name' => 'refund',
            'example' => 'REF-20231201-001'
        ],
        'proforma' => [
            'prefix' => 'PRO',
            'pattern' => '{prefix}-{date}-{sequence}',
            'sequence_name' => 'proforma',
            'example' => 'PRO-20231201-001'
        ]
    ];

    /**
     * Generate a number based on type
     */
    public function generate(string $type = 'invoice'): string
    {
        if (!isset(self::FORMATS[$type])) {
            throw new \Exception("Invalid number type: {$type}");
        }

        $format = self::FORMATS[$type];
        $date = Carbon::now();
        
        // Get or create sequence for today
        $sequence = $this->getDailySequence($format['sequence_name']);
        
        // Build the number
        return $this->buildNumber($format, $date, $sequence);
    }

    /**
     * Generate both invoice and receipt numbers for a sale
     */
    public function generateForSale(): array
    {
        return [
            'invoice' => $this->generate('invoice'),
            'receipt' => $this->generate('receipt'),
            'timestamp' => Carbon::now()->toISOString()
        ];
    }

    /**
     * Get daily sequence number
     */
    private function getDailySequence(string $sequenceName): int
    {
        $today = Carbon::today()->format('Y-m-d');
        
        // Try to find existing sequence for today
        $sequence = NumberSequence::where('name', $sequenceName)
            ->where('date', $today)
            ->first();
        
        if ($sequence) {
            $nextNumber = $sequence->current_number + 1;
            $sequence->update(['current_number' => $nextNumber]);
            return $nextNumber;
        }
        
        // Create new sequence for today
        NumberSequence::create([
            'name' => $sequenceName,
            'date' => $today,
            'current_number' => 1,
            'reset_type' => 'daily'
        ]);
        
        return 1;
    }

    /**
     * Build the number from format
     */
    private function buildNumber(array $format, Carbon $date, int $sequence): string
    {
        $placeholders = [
            '{prefix}' => $format['prefix'],
            '{date}' => $date->format('Ymd'),
            '{year}' => $date->format('Y'),
            '{month}' => $date->format('m'),
            '{day}' => $date->format('d'),
            '{sequence}' => str_pad($sequence, 3, '0', STR_PAD_LEFT),
            '{time}' => $date->format('His'),
            '{random}' => strtoupper(substr(md5(microtime()), 0, 6))
        ];
        
        $number = $format['pattern'];
        
        foreach ($placeholders as $placeholder => $value) {
            $number = str_replace($placeholder, $value, $number);
        }
        
        return $number;
    }

    /**
     * Validate number format
     */
    public function validateNumber(string $number, string $type = 'invoice'): bool
    {
        if (!isset(self::FORMATS[$type])) {
            return false;
        }

        $format = self::FORMATS[$type];
        $pattern = $this->buildRegexPattern($format['pattern']);
        
        return preg_match($pattern, $number) === 1;
    }

    /**
     * Build regex pattern from number pattern
     */
    private function buildRegexPattern(string $pattern): string
    {
        $pattern = str_replace(
            ['{prefix}', '{date}', '{year}', '{month}', '{day}', '{sequence}', '{time}', '{random}'],
            ['([A-Z]{2,4})', '(\d{8})', '(\d{4})', '(\d{2})', '(\d{2})', '(\d{3})', '(\d{6})', '([A-Z0-9]{6})'],
            $pattern
        );
        
        return '/^' . $pattern . '$/';
    }

    /**
     * Parse number to get its components
     */
    public function parseNumber(string $number): ?array
    {
        foreach (self::FORMATS as $type => $format) {
            $pattern = $this->buildRegexPattern($format['pattern']);
            
            if (preg_match($pattern, $number, $matches)) {
                return [
                    'type' => $type,
                    'prefix' => $matches[1] ?? null,
                    'date' => $matches[2] ?? null,
                    'sequence' => (int) ($matches[3] ?? 0),
                    'full_number' => $number,
                    'is_valid' => true
                ];
            }
        }
        
        return [
            'type' => 'unknown',
            'full_number' => $number,
            'is_valid' => false
        ];
    }

    /**
     * Get the next available number for a type
     */
    public function getNextNumber(string $type = 'invoice'): string
    {
        $format = self::FORMATS[$type];
        $date = Carbon::now();
        $sequence = $this->getDailySequence($format['sequence_name']);
        
        return $this->buildNumber($format, $date, $sequence);
    }

    /**
     * Generate refund number based on original invoice
     */
    public function generateRefundForInvoice(string $originalInvoice): string
    {
        // Example: INV-20231201-001 -> REF-20231201-001
        $parts = explode('-', $originalInvoice);
        
        if (count($parts) >= 3) {
            $datePart = $parts[1];
            $sequencePart = end($parts);
            
            return sprintf('REF-%s-%s', $datePart, $sequencePart);
        }
        
        // Fallback to regular refund number
        return $this->generate('refund');
    }

    /**
     * Get number statistics
     */
    public function getStatistics(): array
    {
        $today = Carbon::today()->format('Y-m-d');
        $sequences = NumberSequence::where('date', $today)->get();
        
        $stats = [];
        foreach ($sequences as $sequence) {
            $stats[$sequence->name] = [
                'current' => $sequence->current_number,
                'date' => $sequence->date,
                'reset_type' => $sequence->reset_type
            ];
        }
        
        return [
            'date' => $today,
            'sequences' => $stats,
            'total_sequences' => $sequences->count(),
            'total_numbers_today' => $sequences->sum('current_number')
        ];
    }

    /**
     * Reset sequence manually
     */
    public function resetSequence(string $name): bool
    {
        $today = Carbon::today()->format('Y-m-d');
        
        $sequence = NumberSequence::where('name', $name)
            ->where('date', $today)
            ->first();
        
        if ($sequence) {
            $sequence->update(['current_number' => 0]);
            return true;
        }
        
        return false;
    }

    /**
     * Get example numbers for all types
     */
    public function getExamples(): array
    {
        $examples = [];
        foreach (self::FORMATS as $type => $format) {
            $examples[$type] = [
                'pattern' => $format['pattern'],
                'example' => $format['example'],
                'description' => $this->getTypeDescription($type)
            ];
        }
        
        return $examples;
    }

    /**
     * Get description for number type
     */
    private function getTypeDescription(string $type): string
    {
        $descriptions = [
            'invoice' => 'Official invoice for accounting purposes',
            'receipt' => 'Customer receipt for proof of purchase',
            'credit_note' => 'Credit note for returns or adjustments',
            'refund' => 'Refund transaction',
            'proforma' => 'Proforma invoice for quotations'
        ];
        
        return $descriptions[$type] ?? 'Unknown document type';
    }
}