<?php

namespace App\Services\Providers;

use App\Models\Transaction;
use App\Services\Providers\Contracts\ProviderInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class VtpassProvider extends AbstractProvider implements ProviderInterface
{
    public const ENDPOINT_BALANCE = '/api/balance';
    public const ENDPOINT_PAY = '/api/pay';
    public const ENDPOINT_REQUERY = '/api/requery';
    public const ENDPOINT_SERVICE_VARIATIONS = '/api/service-variations';
    public const ENDPOINT_MERCHANT_VERIFY = '/api/merchant-verify';

    protected function defaultBaseUrl(): ?string
    {
        return 'https://vtpass.com';
    }

    protected function headers(): array
    {
        return [];
    }

    protected function headersForMethod(string $method): array
    {
        $method = strtoupper($method);

        if ($method === 'GET') {
            return array_filter([
                'api-key' => $this->vendor->vtpass_api_key,
                'public-key' => $this->vendor->vtpass_public_key,
            ], fn ($value) => filled($value));
        }

        return array_filter([
            'api-key' => $this->vendor->vtpass_api_key,
            'secret-key' => $this->vendor->vtpass_secret_key,
        ], fn ($value) => filled($value));
    }

    public function getBalance(?Transaction $transaction = null): array
    {
        $result = $this->request('GET', self::ENDPOINT_BALANCE, [], $transaction);
        $httpCode = Arr::get($result, 'http_code');
        $body = Arr::get($result, 'body', []);
        $httpOk = is_numeric($httpCode) && (int) $httpCode >= 200 && (int) $httpCode < 300;
        $code = (string) data_get($body, 'code', '');
        $responseDescription = Str::lower((string) data_get($body, 'response_description', ''));
        $balance = data_get($body, 'contents.balance', data_get($body, 'content.balance'));

        return [
            'success' => ! Arr::get($result, 'hard_failure', false)
                && $httpOk
                && (
                    in_array($code, ['000', '0000'], true)
                    || str_contains($responseDescription, 'successful')
                    || is_numeric($balance)
                ),
            'body' => $body,
            'http_code' => $httpCode,
            'duration_ms' => Arr::get($result, 'duration_ms'),
        ];
    }

    public function getPlans(?Transaction $transaction = null): array
    {
        return $this->getVariationCodes('mtn', $transaction);
    }

    public function getVariationCodes(string $serviceID, ?Transaction $transaction = null): array
    {
        $result = $this->request('GET', self::ENDPOINT_SERVICE_VARIATIONS, [
            'serviceID' => $serviceID,
        ], $transaction);

        $httpCode = Arr::get($result, 'http_code');
        $body = Arr::get($result, 'body', []);
        $successCode = (string) data_get($body, 'code', '');
        $responseDescription = Str::lower((string) data_get($body, 'response_description', ''));
        $variations = data_get($body, 'content.variations', data_get($body, 'content', []));
        $hasVariations = is_array($variations) && count($variations) > 0;
        $httpOk = is_numeric($httpCode) && (int) $httpCode >= 200 && (int) $httpCode < 300;

        return [
            'success' => ! Arr::get($result, 'hard_failure', false)
                && $httpOk
                && (
                    in_array($successCode, ['000', '0000'], true)
                    || str_contains($responseDescription, 'success')
                    || $hasVariations
                ),
            'body' => $body,
            'http_code' => $httpCode,
            'duration_ms' => Arr::get($result, 'duration_ms'),
        ];
    }

    public function purchaseData(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        return $this->normalizeVtpassPurchaseResult(
            $this->request('POST', self::ENDPOINT_PAY, $payload, $transaction)
        );
    }

    public function purchaseAirtime(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        return $this->normalizeVtpassPurchaseResult(
            $this->request('POST', self::ENDPOINT_PAY, $payload, $transaction)
        );
    }

    public function purchaseCable(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        return $this->normalizeVtpassPurchaseResult(
            $this->request('POST', self::ENDPOINT_PAY, $payload, $transaction)
        );
    }

    public function validateIuc(array $payload, ?Transaction $transaction = null): array
    {
        $requestPayload = array_filter([
            'billersCode' => Arr::get($payload, 'billersCode') ?: Arr::get($payload, 'smart_card_number'),
            'serviceID' => Arr::get($payload, 'serviceID') ?: Arr::get($payload, 'cablename'),
        ], fn ($value) => $value !== null && $value !== '');

        $result = $this->request('POST', self::ENDPOINT_MERCHANT_VERIFY, $requestPayload, $transaction);
        $httpCode = Arr::get($result, 'http_code');
        $body = Arr::get($result, 'body', []);
        $code = (string) data_get($body, 'code', '');
        $responseDescription = Str::lower((string) data_get($body, 'response_description', ''));
        $httpOk = is_numeric($httpCode) && (int) $httpCode >= 200 && (int) $httpCode < 300;

        return [
            'success' => ! Arr::get($result, 'hard_failure', false)
                && $httpOk
                && (
                    in_array($code, ['000', '0000'], true)
                    || str_contains($responseDescription, 'successful')
                ),
            'body' => $body,
            'http_code' => $httpCode,
            'duration_ms' => Arr::get($result, 'duration_ms'),
        ];
    }

    public function purchaseElectricity(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        return $this->normalizeVtpassPurchaseResult(
            $this->request('POST', self::ENDPOINT_PAY, $payload, $transaction)
        );
    }

    public function validateMeter(array $payload, ?Transaction $transaction = null): array
    {
        $requestPayload = array_filter([
            'billersCode' => Arr::get($payload, 'billersCode') ?: Arr::get($payload, 'meternumber') ?: Arr::get($payload, 'meter_number'),
            'serviceID' => Arr::get($payload, 'serviceID') ?: Arr::get($payload, 'disconame') ?: Arr::get($payload, 'disco_name'),
            'type' => Arr::get($payload, 'type') ?: Arr::get($payload, 'mtype'),
        ], fn ($value) => $value !== null && $value !== '');

        $result = $this->request('POST', self::ENDPOINT_MERCHANT_VERIFY, $requestPayload, $transaction);
        $httpCode = Arr::get($result, 'http_code');
        $body = Arr::get($result, 'body', []);
        $code = (string) data_get($body, 'code', '');
        $responseDescription = Str::lower((string) data_get($body, 'response_description', ''));
        $httpOk = is_numeric($httpCode) && (int) $httpCode >= 200 && (int) $httpCode < 300;

        return [
            'success' => ! Arr::get($result, 'hard_failure', false)
                && $httpOk
                && (
                    in_array($code, ['000', '0000'], true)
                    || str_contains($responseDescription, 'successful')
                ),
            'body' => $body,
            'http_code' => $httpCode,
            'duration_ms' => Arr::get($result, 'duration_ms'),
        ];
    }

    public function purchaseEpin(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        return $this->normalizeVtpassPurchaseResult(
            $this->request('POST', self::ENDPOINT_PAY, $payload, $transaction)
        );
    }

    public function queryTransaction(array $payload, ?Transaction $transaction = null): ProviderResponse
    {
        $requestId = (string) (Arr::get($payload, 'internal_reference')
            ?: Arr::get($payload, 'request_id')
            ?: Arr::get($payload, 'reference'));

        return $this->normalizeVtpassQueryResult(
            $this->request('POST', self::ENDPOINT_REQUERY, [
                'request_id' => $requestId,
            ], $transaction)
        );
    }

    protected function normalizeVtpassPurchaseResult(array $result): ProviderResponse
    {
        return $this->normalizeVtpassResponse($result, 'purchase');
    }

    protected function normalizeVtpassQueryResult(array $result): ProviderResponse
    {
        return $this->normalizeVtpassResponse($result, 'requery');
    }

    protected function normalizeVtpassResponse(array $result, string $context): ProviderResponse
    {
        $body = Arr::get($result, 'body', []);
        $httpCode = Arr::get($result, 'http_code');
        $durationMs = Arr::get($result, 'duration_ms');
        $hardFailure = (bool) Arr::get($result, 'hard_failure', false);
        $endpoint = Arr::get($result, 'endpoint');

        $code = (string) data_get($body, 'code', '');
        $txStatusRaw = data_get($body, 'content.transactions.status')
            ?? data_get($body, 'content.status')
            ?? data_get($body, 'status');
        $txStatus = Str::lower((string) $txStatusRaw);

        $message = (string) (
            data_get($body, 'response_description')
            ?? data_get($body, 'content.transactions.status')
            ?? data_get($body, 'message')
            ?? ($hardFailure ? 'VTpass request failed' : 'VTpass request response received')
        );
        $messageLower = Str::lower($message);

        $vendorReference = $this->extractVtpassVendorReference($body);
        $vendorCost = $this->extractVtpassVendorCost($body);

        $pendingSignals = ['pending', 'processing', 'initiated', 'queued', 'timeout', 'no response'];
        $isPendingMessage = collect($pendingSignals)->contains(fn ($needle) => str_contains($messageLower, $needle));
        $isPendingStatus = collect(['pending', 'processing', 'queued', 'initiated'])->contains(fn ($needle) => $txStatus === $needle || str_contains($txStatus, $needle));
        $isSuccessStatus = collect(['delivered', 'success'])->contains(fn ($needle) => $txStatus === $needle || str_contains($txStatus, $needle));

        if ($code === '000' && $isSuccessStatus) {
            return ProviderResponse::success(
                message: $message,
                vendorReference: $vendorReference,
                vendorCost: $vendorCost,
                raw: $body,
                httpCode: $httpCode,
                durationMs: $durationMs,
                endpoint: $endpoint
            );
        }

        if ($code === '000' && $txStatus === '' && str_contains($messageLower, 'successful')) {
            return ProviderResponse::success(
                message: $message,
                vendorReference: $vendorReference,
                vendorCost: $vendorCost,
                raw: $body,
                httpCode: $httpCode,
                durationMs: $durationMs,
                endpoint: $endpoint
            );
        }

        if ($hardFailure || $isPendingStatus || $isPendingMessage) {
            return ProviderResponse::pending(
                message: $message,
                vendorReference: $vendorReference,
                vendorCost: $vendorCost,
                raw: $body,
                httpCode: $httpCode,
                durationMs: $durationMs,
                hardFailure: $hardFailure,
                endpoint: $endpoint
            );
        }

        if ($code === '000' && $txStatus === '' && $context === 'requery') {
            // Some VTpass responses may return success code without a detailed nested status.
            return ProviderResponse::success(
                message: $message,
                vendorReference: $vendorReference,
                vendorCost: $vendorCost,
                raw: $body,
                httpCode: $httpCode,
                durationMs: $durationMs,
                endpoint: $endpoint
            );
        }

        return ProviderResponse::failed(
            message: $message,
            raw: $body,
            httpCode: $httpCode,
            durationMs: $durationMs,
            hardFailure: false,
            vendorReference: $vendorReference,
            vendorCost: $vendorCost,
            endpoint: $endpoint
        );
    }

    protected function extractVtpassVendorReference(array $body): ?string
    {
        $value = data_get($body, 'content.transactions.transactionId')
            ?? data_get($body, 'content.transactions.transaction_id')
            ?? data_get($body, 'content.transactions.id')
            ?? data_get($body, 'transactionId')
            ?? data_get($body, 'transaction_id');

        return filled($value) ? (string) $value : null;
    }

    protected function extractVtpassVendorCost(array $body): ?float
    {
        $amount = data_get($body, 'content.transactions.amount')
            ?? data_get($body, 'content.amount')
            ?? data_get($body, 'amount');

        $commission = data_get($body, 'content.transactions.commission')
            ?? data_get($body, 'content.commission')
            ?? data_get($body, 'commission');

        if (is_numeric($amount) && is_numeric($commission)) {
            $netCost = round((float) $amount - (float) $commission, 2);
            if ($netCost >= 0) {
                return $netCost;
            }
        }

        return is_numeric($amount) ? (float) $amount : null;
    }
}
