<?php

namespace App\Services;

use App\Models\Order;
use App\Models\OrderItem;
use App\Models\ProviderFulfillmentAttempt;
use App\Models\ProviderProductMapping;
use App\Models\StockCode;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;
use Throwable;

class ProviderFulfillmentService
{
    public function __construct(
        private MockProviderFulfillmentService $mock,
        private IntegrationSignature $signature,
        private NotificationService $notifications,
        private MidasbuyProviderClient $midasbuy,
    ) {}

    public function fulfillOrder(Order $order, User $actor): Order
    {
        $hasMappedItems = $order->items()
            ->whereHas('productVariant.providerMappings', fn ($query) => $query->where('is_active', true))
            ->exists();

        if (! $hasMappedItems && config('raseednow.fulfillment.driver') === 'mock_provider') {
            return $this->mock->fulfillOrder($order, $actor);
        }

        return DB::transaction(function () use ($order, $actor) {
            $order = Order::whereKey($order->id)->with('items')->lockForUpdate()->firstOrFail();

            if ($order->payment_status !== 'paid') {
                throw new RuntimeException('Provider fulfillment requires a paid order.');
            }

            $handled = 0;

            foreach ($order->items as $item) {
                $mapping = $item->productVariant?->providerMappings()
                    ->with('provider')
                    ->where('is_active', true)
                    ->whereHas('provider', fn ($query) => $query->where('is_active', true))
                    ->first();

                if ($mapping) {
                    $this->fulfillMappedItem($item, $order, $mapping);
                    $handled++;
                    continue;
                }

                if (! in_array($item->delivery_method_snapshot, ['api', 'hybrid'], true)) {
                    continue;
                }

                $this->fulfillItem($item, $order);
                $handled++;
            }

            if ($handled === 0) {
                throw new RuntimeException('No API or hybrid items were found on this order.');
            }

            $allCompleted = $order->items()->where('fulfillment_status', '!=', 'completed')->doesntExist();

            $order->update([
                'assigned_to' => $order->assigned_to ?: $actor->id,
                'claimed_at' => $order->claimed_at ?? now(),
                'fulfillment_status' => $allCompleted ? 'completed' : 'claimed',
                'completed_at' => $allCompleted ? now() : $order->completed_at,
            ]);

            $order->timelines()->create([
                'actor_id' => $actor->id,
                'event' => 'provider_fulfilled',
                'title' => 'تم تنفيذ بنود API عبر مزود الإنتاج',
                'note' => 'Provider fulfilled '.$handled.' item(s).',
            ]);

            if ($allCompleted) {
                $this->notifications->fulfillmentCompleted($order);
            }

            return $order->fresh(['items.latestProviderAttempt']);
        });
    }

    private function fulfillItem(OrderItem $item, Order $order): ProviderFulfillmentAttempt
    {
        $reference = 'PRV-'.now()->format('ymd').'-'.Str::upper(Str::random(8));
        $payload = [
            'reference' => $reference,
            'order_number' => $order->order_number,
            'sku' => $item->sku_snapshot,
            'quantity' => $item->quantity,
            'customer_fields' => $order->customer_fields,
        ];

        $responsePayload = $this->sendProviderRequest($payload);
        $status = data_get($responsePayload, 'status', 'completed');
        $completed = in_array($status, ['completed', 'success', 'fulfilled'], true);

        $attempt = $item->providerAttempts()->create([
            'provider' => 'generic_http',
            'reference' => data_get($responsePayload, 'reference', data_get($responsePayload, 'provider_reference', $reference)),
            'status' => $completed ? 'completed' : 'failed',
            'request_payload' => $payload,
            'response_payload' => $responsePayload,
            'completed_at' => $completed ? now() : null,
            'failed_at' => $completed ? null : now(),
        ]);

        if (! $completed) {
            $item->update(['fulfillment_status' => 'failed']);

            throw new RuntimeException('Provider failed to fulfill '.$item->sku_snapshot.'.');
        }

        $item->update([
            'fulfillment_status' => 'completed',
            'fulfillment_payload' => [
                'provider' => 'generic_http',
                'provider_reference' => $attempt->reference,
                'completed_at' => now()->toIso8601String(),
            ],
        ]);

        return $attempt;
    }

    private function fulfillMappedItem(OrderItem $item, Order $order, ProviderProductMapping $mapping): ProviderFulfillmentAttempt
    {
        if ($mapping->fulfillment_type !== 'pubg_uc_code' || $mapping->provider->type !== 'midasbuy') {
            return $this->fulfillItem($item, $order);
        }

        $provider = $mapping->provider;
        $playerId = data_get($order->customer_fields, 'player_id');

        if (blank($playerId)) {
            $item->update(['fulfillment_status' => 'failed']);

            throw new RuntimeException('PUBG fulfillment requires player_id in order customer fields.');
        }

        $reference = 'MID-'.now()->format('ymd').'-'.Str::upper(Str::random(8));
        $codeCount = max(1, min(5, (int) ($mapping->codes_per_order ?: $item->quantity)));
        $codes = $this->reserveUcCodes($item, $order, $provider->mode === 'mock' ? $codeCount : $codeCount, $provider->mode === 'mock');
        $plainCodes = collect($codes)->map(fn ($code) => is_string($code) ? $code : $code->decryptedCode())->values()->all();

        $payload = [
            'reference' => $reference,
            'provider' => $provider->code,
            'mode' => $provider->mode,
            'player_id' => $playerId,
            'order_number' => $order->order_number,
            'sku' => $mapping->provider_sku ?: $item->sku_snapshot,
            'codes_count' => count($plainCodes),
        ];

        $attempt = $item->providerAttempts()->create([
            'provider_id' => $provider->id,
            'provider' => $provider->code,
            'reference' => $reference,
            'status' => 'queued',
            'request_payload' => array_merge($payload, [
                'uc_codes' => collect($plainCodes)->map(fn ($code) => $this->mask($code))->all(),
            ]),
        ]);

        try {
            if (($mapping->mode === 'bulk' || count($plainCodes) > 1) && count($plainCodes) <= 5) {
                $responsePayload = $this->midasbuy->bulkActivate($provider, $playerId, $plainCodes, $order, $item);
                $completed = data_get($responsePayload, 'status') === 'success';
            } elseif ($mapping->mode === 'async') {
                $responsePayload = $this->midasbuy->activateAsTask($provider, $playerId, $plainCodes[0], $order, $item);
                $completed = in_array(data_get($responsePayload, 'status'), ['completed', 'success'], true);
            } else {
                $responsePayload = $this->midasbuy->activate($provider, $playerId, $plainCodes[0], $order, $item);
                $completed = in_array(data_get($responsePayload, 'status'), ['success', 'completed'], true);
            }
        } catch (Throwable $exception) {
            $this->releaseUcCodes($codes);
            $attempt->update([
                'status' => 'failed',
                'response_payload' => ['error' => $exception->getMessage()],
                'failed_at' => now(),
            ]);
            $item->update(['fulfillment_status' => 'failed']);

            throw new RuntimeException('Midasbuy fulfillment failed: '.$exception->getMessage());
        }

        if (! $completed) {
            $this->releaseUcCodes($codes);
            $attempt->update([
                'status' => 'failed',
                'response_payload' => $responsePayload,
                'failed_at' => now(),
            ]);
            $item->update(['fulfillment_status' => 'failed']);

            throw new RuntimeException('Midasbuy returned an incomplete activation result.');
        }

        $this->sellUcCodes($codes, $item);

        $attempt->update([
            'status' => 'completed',
            'response_payload' => $responsePayload,
            'completed_at' => now(),
        ]);

        $item->update([
            'fulfillment_status' => 'completed',
            'fulfillment_payload' => [
                'provider' => $provider->code,
                'provider_reference' => $reference,
                'player_id' => $playerId,
                'stock_code_ids' => collect($codes)->filter(fn ($code) => $code instanceof StockCode)->pluck('id')->values()->all(),
                'completed_at' => now()->toIso8601String(),
            ],
        ]);

        return $attempt->fresh();
    }

    private function reserveUcCodes(OrderItem $item, Order $order, int $count, bool $allowMockFallback): array
    {
        $codes = StockCode::where('product_variant_id', $item->product_variant_id)
            ->where('status', 'available')
            ->lockForUpdate()
            ->limit($count)
            ->get();

        if ($codes->count() < $count) {
            if ($allowMockFallback) {
                return collect(range(1, $count))->map(fn ($index) => 'MOCK-UC-'.$item->id.'-'.$index.'-'.Str::upper(Str::random(6)))->all();
            }

            throw new RuntimeException('No available UC codes for '.$item->sku_snapshot.'. Import UC codes before switching this provider to Live.');
        }

        foreach ($codes as $code) {
            $code->update([
                'status' => 'reserved',
                'reserved_order_id' => $order->id,
                'sold_order_item_id' => $item->id,
                'reserved_at' => now(),
                'check_status' => 'reserved_for_provider',
            ]);
        }

        return $codes->all();
    }

    private function sellUcCodes(array $codes, OrderItem $item): void
    {
        foreach ($codes as $code) {
            if (! $code instanceof StockCode) {
                continue;
            }

            $code->update([
                'status' => 'sold',
                'sold_order_item_id' => $item->id,
                'sold_at' => now(),
                'check_status' => 'activated',
                'checked_at' => now(),
            ]);
        }
    }

    private function releaseUcCodes(array $codes): void
    {
        foreach ($codes as $code) {
            if (! $code instanceof StockCode) {
                continue;
            }

            $code->update([
                'status' => 'available',
                'reserved_order_id' => null,
                'sold_order_item_id' => null,
                'reserved_at' => null,
                'check_status' => 'released_after_provider_error',
            ]);
        }
    }

    private function mask(string $value): string
    {
        if (mb_strlen($value) <= 8) {
            return str_repeat('*', max(4, mb_strlen($value)));
        }

        return mb_substr($value, 0, 4).str_repeat('*', max(4, mb_strlen($value) - 8)).mb_substr($value, -4);
    }

    private function sendProviderRequest(array $payload): array
    {
        $endpoint = config('raseednow.fulfillment.generic_http.endpoint');

        if (blank($endpoint)) {
            throw new RuntimeException('Fulfillment provider endpoint is not configured.');
        }

        $signature = $this->signature->sign($payload, config('raseednow.fulfillment.generic_http.api_secret'));

        try {
            $response = Http::timeout(config('raseednow.fulfillment.generic_http.timeout'))
                ->withHeaders(array_filter([
                    'Accept' => 'application/json',
                    'X-Api-Key' => config('raseednow.fulfillment.generic_http.api_key'),
                    'X-Signature' => $signature,
                ]))
                ->post($endpoint, $payload);
        } catch (Throwable $exception) {
            throw new RuntimeException('Provider request failed: '.$exception->getMessage());
        }

        if (! $response->successful()) {
            throw new RuntimeException('Provider rejected the request.');
        }

        return $response->json() ?: ['status' => 'completed'];
    }
}
