<?php

namespace Daniv04\HaciendaPackage\Traits;

use Daniv04\HaciendaPackage\Models\ReceiptConsecutive;
use Illuminate\Support\Facades\DB;

trait ConsecutiveNumberTrait
{
    private int $maxRetries = 3;
    private int $baseDelayMs = 100;

    protected function getIncrementedConsecutiveNumber(): string
    {
        return $this->retryWithExponentialBackoff(function () {
            return DB::transaction(function () {
                $record = ReceiptConsecutive::lockForUpdate()->first();
                if (!$record) {
                    $record = ReceiptConsecutive::create(['last_number' => 1]);
                    return str_pad(1, 10, '0', STR_PAD_LEFT);
                }
                $record->increment('last_number');
                return str_pad($record->last_number, 10, '0', STR_PAD_LEFT);
            });
        });
    }

    protected function getConsecutiveNumber(): string
    {
        return $this->retryWithExponentialBackoff(function () {
            return DB::transaction(function () {
                $record = ReceiptConsecutive::lockForUpdate()->first();
                if (!$record) {
                    $record = ReceiptConsecutive::create(['last_number' => 1]);
                    return str_pad(1, 10, '0', STR_PAD_LEFT);
                }
                return str_pad($record->last_number, 10, '0', STR_PAD_LEFT);
            });
        });
    }

    private function retryWithExponentialBackoff(callable $callback): string
    {
        $attempt = 0;
        $lastException = null;

        while ($attempt < $this->maxRetries) {
            try {
                return $callback();
            } catch (\Throwable $e) {
                $lastException = $e;
                // Check if it's a lock timeout error
                if (!$this->isLockTimeoutError($e)) {
                    throw $e;
                }

                $attempt++;
                if ($attempt < $this->maxRetries) {
                    // Exponential backoff: 100ms, 200ms, 400ms
                    $delayMs = $this->baseDelayMs * (2 ** ($attempt - 1));
                    usleep($delayMs * 1000);
                }
            }
        }

        throw $lastException ?? new \RuntimeException('Failed to acquire consecutive number after ' . $this->maxRetries . ' retries');
    }

    private function isLockTimeoutError(\Throwable $e): bool
    {
        $message = strtolower($e->getMessage());
        // MySQL lock timeout messages
        if (str_contains($message, 'lock wait timeout') ||
            str_contains($message, 'deadlock') ||
            str_contains($message, 'statement aborted')) {
            return true;
        }
        // PostgreSQL lock timeout messages
        if (str_contains($message, 'canceling statement') ||
            str_contains($message, 'deadlock detected')) {
            return true;
        }
        // Check exception code for database-specific errors
        if (method_exists($e, 'getCode')) {
            $code = (string) $e->getCode();
            // MySQL: 1205 (lock timeout), 1213 (deadlock)
            if (in_array($code, ['1205', '1213'])) {
                return true;
            }
            // PostgreSQL: 40P01 (deadlock)
            if ($code === '40P01') {
                return true;
            }
        }
        return false;
    }
}
