<?php

namespace Kylin\Payment\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Kylin\Payment\Facades\Payment;
use Illuminate\Contracts\Container\Container;

/**
 * 支付重试服务类
 */
class PaymentRetryService
{
    /**
     * 默认重试配置
     */
    const DEFAULT_RETRY_CONFIG = [
        'max_attempts' => 3,
        'delay' => 60,
        'backoff_factor' => 2,
        'max_delay' => 3600,
    ];

    public function __construct(
        protected readonly Container $app
    ) {
    }

    public function createRetryRecord(int $paymentRecordId, array $config = []): bool
    {
        $config = array_merge(self::DEFAULT_RETRY_CONFIG, $config);

        return DB::table('payment_retries')->insert([
            'payment_record_id' => $paymentRecordId,
            'attempts' => 0,
            'max_attempts' => $config['max_attempts'],
            'delay' => $config['delay'],
            'backoff_factor' => $config['backoff_factor'],
            'max_delay' => $config['max_delay'],
            'status' => 'pending',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    public function retryPayment(int $paymentRecordId, array $options = []): bool
    {
        $record = Payment::paymentRecord()->getById($paymentRecordId);
        if (!$record) {
            throw new \Exception('Payment record not found');
        }

        if (!in_array($record->status, ['failed', 'pending'])) {
            throw new \Exception('Payment status cannot be retried');
        }

        $retryRecord = DB::table('payment_retries')
            ->where('payment_record_id', $paymentRecordId)
            ->where('status', '!=', 'completed')
            ->first();

        if (!$retryRecord) {
            $this->createRetryRecord($paymentRecordId, $options);
            $retryRecord = DB::table('payment_retries')
                ->where('payment_record_id', $paymentRecordId)
                ->first();
        }

        if ($retryRecord->attempts >= $retryRecord->max_attempts) {
            DB::table('payment_retries')
                ->where('id', $retryRecord->id)
                ->update([
                    'status' => 'failed',
                    'updated_at' => now(),
                ]);
            throw new \Exception('Maximum retry attempts reached');
        }

        $attempts = $retryRecord->attempts + 1;
        $delay = min(
            $retryRecord->delay * pow($retryRecord->backoff_factor, $attempts - 1),
            $retryRecord->max_delay
        );

        DB::table('payment_retries')
            ->where('id', $retryRecord->id)
            ->update([
                'attempts' => $attempts,
                'status' => 'processing',
                'updated_at' => now(),
            ]);

        try {
            $payload = json_decode($record->payload, true);
            $result = Payment::pay($record->payment_type, $payload);

            DB::table('payment_retries')
                ->where('id', $retryRecord->id)
                ->update([
                    'status' => 'completed',
                    'last_result' => json_encode($result),
                    'updated_at' => now(),
                ]);

            return true;
        } catch (\Exception $e) {
            DB::table('payment_retries')
                ->where('id', $retryRecord->id)
                ->update([
                    'status' => $attempts >= $retryRecord->max_attempts ? 'failed' : 'pending',
                    'last_error' => $e->getMessage(),
                    'updated_at' => now(),
                ]);

            if ($attempts < $retryRecord->max_attempts) {
                $this->queueRetry($paymentRecordId, $delay);
            }

            throw $e;
        }
    }

    public function queueRetry(int $paymentRecordId, int $delay = 60): void
    {
        Queue::later($delay, function () use ($paymentRecordId) {
            try {
                $this->retryPayment($paymentRecordId);
            } catch (\Exception $e) {
                Payment::log('retry', 'failed', ['payment_record_id' => $paymentRecordId], $e->getMessage());
            }
        });
    }

    public function processFailedPayments(int $hours = 24): int
    {
        $records = DB::table('payment_records')
            ->where('status', 'failed')
            ->where('created_at', '>=', now()->subHours($hours))
            ->get();

        $count = 0;

        foreach ($records as $record) {
            $retryRecord = DB::table('payment_retries')
                ->where('payment_record_id', $record->id)
                ->first();

            if (!$retryRecord) {
                $this->createRetryRecord($record->id);
                $this->queueRetry($record->id);
                $count++;
            }
        }

        return $count;
    }

    public function getRetryStats(): array
    {
        return [
            'total' => DB::table('payment_retries')->count(),
            'pending' => DB::table('payment_retries')->where('status', 'pending')->count(),
            'processing' => DB::table('payment_retries')->where('status', 'processing')->count(),
            'completed' => DB::table('payment_retries')->where('status', 'completed')->count(),
            'failed' => DB::table('payment_retries')->where('status', 'failed')->count(),
        ];
    }

    public function getRetryRecords(?int $paymentRecordId = null): \Illuminate\Database\Query\Builder
    {
        $query = DB::table('payment_retries');

        if ($paymentRecordId) {
            $query->where('payment_record_id', $paymentRecordId);
        }

        return $query->orderBy('created_at', 'desc');
    }

    public function manualRetry(int $paymentRecordId): bool
    {
        return $this->retryPayment($paymentRecordId);
    }

    public function cancelRetry(int $paymentRecordId): bool
    {
        return DB::table('payment_retries')
            ->where('payment_record_id', $paymentRecordId)
            ->where('status', '!=', 'completed')
            ->update([
                'status' => 'cancelled',
                'updated_at' => now(),
            ]);
    }
}
