<?php

namespace App\Services;

use App\Models\BillPayment;
use App\Models\Tenant;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;
use Throwable;

class VtpassBillPaymentService
{
    private const PENDING_CODES = ['001', '044', '089', '099'];

    private const FAILED_CODES = [
        '010', '011', '012', '013', '014', '015', '016', '017', '018', '019',
        '021', '022', '023', '024', '025', '026', '027', '028', '030', '031',
        '032', '034', '035', '083', '085', '087', '091',
    ];

    public function __construct(private readonly PlatformSettings $settings)
    {
    }

    public function isConfigured(?Tenant $tenant): bool
    {
        $settings = $this->settings->billPaymentSettings();

        return ($settings['driver'] ?? 'manual_provider') === 'vtpass'
            && filled($settings['vtpass_api_key'] ?? null)
            && filled($settings['vtpass_secret_key'] ?? null)
            && filled($settings['vtpass_public_key'] ?? null);
    }

    public function purchase(BillPayment $payment): array
    {
        $payment->loadMissing('tenant');
        $tenant = $payment->tenant;

        if (! $this->isConfigured($tenant)) {
            return $this->failed('VTpass is selected, but API key, public key, or secret key is missing.', [
                'driver' => 'vtpass',
                'configured' => false,
            ]);
        }

        try {
            $payload = $this->payloadFor($payment);
        } catch (RuntimeException $exception) {
            return $this->failed($exception->getMessage(), [
                'driver' => 'vtpass',
                'configuration_error' => true,
            ]);
        }

        try {
            $response = Http::withHeaders($this->postHeaders($tenant))
                ->acceptJson()
                ->asJson()
                ->timeout(45)
                ->post($this->endpoint($tenant, 'pay'), $payload);
        } catch (ConnectionException $exception) {
            return $this->pending('VTpass did not return a definite response. Requery this transaction before retrying.', [
                'driver' => 'vtpass',
                'request' => $payload,
                'exception' => $exception->getMessage(),
            ]);
        } catch (Throwable $exception) {
            return $this->failed('VTpass request failed before it could be completed: '.$exception->getMessage(), [
                'driver' => 'vtpass',
                'request' => $payload,
            ]);
        }

        return $this->normalizeResponse($response->json() ?: [
            'http_status' => $response->status(),
            'body' => Str::limit($response->body(), 2000, ''),
        ], $payload);
    }

    public function requery(BillPayment $payment): array
    {
        $payment->loadMissing('tenant');
        $tenant = $payment->tenant;
        $requestId = $payment->provider_reference;

        if (! $this->isConfigured($tenant)) {
            return $this->failed('VTpass credentials are not complete for this tenant.', [
                'driver' => 'vtpass',
                'configured' => false,
            ]);
        }

        if (! filled($requestId)) {
            return $this->failed('This bill payment has no VTpass request ID to requery.', [
                'driver' => 'vtpass',
            ]);
        }

        $payload = ['request_id' => $requestId];

        try {
            $response = Http::withHeaders($this->postHeaders($tenant))
                ->acceptJson()
                ->asJson()
                ->timeout(30)
                ->post($this->endpoint($tenant, 'requery'), $payload);
        } catch (ConnectionException $exception) {
            return $this->pending('VTpass requery could not confirm the final status. Try again later.', [
                'driver' => 'vtpass',
                'request' => $payload,
                'exception' => $exception->getMessage(),
            ]);
        } catch (Throwable $exception) {
            return $this->failed('VTpass requery failed: '.$exception->getMessage(), [
                'driver' => 'vtpass',
                'request' => $payload,
            ]);
        }

        return $this->normalizeResponse($response->json() ?: [
            'http_status' => $response->status(),
            'body' => Str::limit($response->body(), 2000, ''),
        ], $payload);
    }

    public function walletBalance(Tenant $tenant): array
    {
        if (! $this->isConfigured($tenant)) {
            return ['successful' => false, 'message' => 'VTpass credentials are not complete.'];
        }

        try {
            $response = Http::withHeaders($this->getHeaders($tenant))
                ->acceptJson()
                ->timeout(20)
                ->get($this->endpoint($tenant, 'balance'));
        } catch (Throwable $exception) {
            return ['successful' => false, 'message' => $exception->getMessage()];
        }

        $body = $response->json() ?: [];

        return [
            'successful' => $response->successful(),
            'balance' => data_get($body, 'contents.balance'),
            'raw' => $body ?: Str::limit($response->body(), 1000, ''),
        ];
    }

    public function requestId(): string
    {
        return now('Africa/Lagos')->format('YmdHi').Str::upper(Str::random(12));
    }

    public function serviceIdFor(string $serviceType, string $provider): ?string
    {
        $key = Str::of($provider)->lower()->replace(['_', '-'], ' ')->squish()->toString();

        return match ($serviceType) {
            'airtime' => match (true) {
                str_contains($key, 'mtn') => 'mtn',
                str_contains($key, 'airtel') => 'airtel',
                str_contains($key, 'glo') => 'glo',
                str_contains($key, '9mobile'), str_contains($key, 'etisalat') => 'etisalat',
                default => null,
            },
            'data' => match (true) {
                str_contains($key, 'mtn') => 'mtn-data',
                str_contains($key, 'airtel') => 'airtel-data',
                str_contains($key, 'glo') => 'glo-data',
                str_contains($key, '9mobile'), str_contains($key, 'etisalat') => 'etisalat-data',
                str_contains($key, 'spectranet') => 'spectranet',
                str_contains($key, 'smile') => 'smile-direct',
                default => null,
            },
            'electricity' => match (true) {
                str_contains($key, 'ikeja'), str_contains($key, 'ikedc') => 'ikeja-electric',
                str_contains($key, 'eko'), str_contains($key, 'ekedc') => 'eko-electric',
                str_contains($key, 'abuja'), str_contains($key, 'aedc') => 'abuja-electric',
                str_contains($key, 'port harcourt'), str_contains($key, 'phed') => 'portharcourt-electric',
                str_contains($key, 'kano'), str_contains($key, 'kedco') => 'kano-electric',
                str_contains($key, 'ibadan'), str_contains($key, 'ibedc') => 'ibadan-electric',
                str_contains($key, 'enugu'), str_contains($key, 'eedc') => 'enugu-electric',
                str_contains($key, 'benin'), str_contains($key, 'bedc') => 'benin-electric',
                str_contains($key, 'kaduna'), str_contains($key, 'kaedco') => 'kaduna-electric',
                str_contains($key, 'jos'), str_contains($key, 'jed') => 'jos-electric',
                str_contains($key, 'aba') => 'aba-electric',
                str_contains($key, 'yola'), str_contains($key, 'yedc') => 'yola-electric',
                default => null,
            },
            'cable_tv' => match (true) {
                str_contains($key, 'dstv') => 'dstv',
                str_contains($key, 'gotv'), str_contains($key, 'go tv') => 'gotv',
                str_contains($key, 'startimes') => 'startimes',
                default => null,
            },
            'internet' => match (true) {
                str_contains($key, 'spectranet') => 'spectranet',
                str_contains($key, 'smile') => 'smile-direct',
                default => null,
            },
            default => null,
        };
    }

    private function payloadFor(BillPayment $payment): array
    {
        $serviceId = $this->serviceIdFor((string) $payment->service_type, (string) $payment->provider);

        if (! $serviceId) {
            throw new RuntimeException('This provider is not mapped to a VTpass service ID yet.');
        }

        $payload = [
            'request_id' => $payment->provider_reference ?: $this->requestId(),
            'serviceID' => $serviceId,
            'amount' => (float) $payment->amount,
            'phone' => $this->recipientPhone($payment),
        ];

        if ($payment->service_type !== 'airtime') {
            $payload['billersCode'] = $payment->customer_identifier;
            $payload['variation_code'] = $this->variationCodeFor($payment);
        }

        if ($payment->service_type === 'cable_tv') {
            $payload['subscription_type'] = $this->cableSubscriptionType($payment);

            if ($payload['subscription_type'] === 'change') {
                $payload['quantity'] = 1;
            }
        }

        return array_filter($payload, fn ($value) => $value !== null && $value !== '');
    }

    private function variationCodeFor(BillPayment $payment): string
    {
        if ($payment->service_type === 'electricity') {
            return Str::of((string) $payment->package_name)->lower()->contains('post') ? 'postpaid' : 'prepaid';
        }

        $package = trim((string) $payment->package_name);

        if ($package === '') {
            throw new RuntimeException('Enter the VTpass variation code in the package/plan field for this bill type.');
        }

        if (str_contains($package, '|')) {
            return trim((string) Arr::last(explode('|', $package)));
        }

        return $package;
    }

    private function cableSubscriptionType(BillPayment $payment): string
    {
        $package = Str::of((string) $payment->package_name)->lower();

        return $package->contains('renew') ? 'renew' : 'change';
    }

    private function recipientPhone(BillPayment $payment): string
    {
        $candidate = preg_replace('/\D+/', '', (string) $payment->customer_identifier) ?: '';

        if (in_array($payment->service_type, ['airtime', 'data'], true)) {
            return $candidate;
        }

        return preg_replace('/\D+/', '', (string) ($payment->customer?->phone ?? $payment->recordedBy?->phone ?? $candidate)) ?: $candidate;
    }

    private function normalizeResponse(array $body, array $request): array
    {
        $code = str_pad((string) data_get($body, 'code', data_get($body, 'response_description', '')), 3, '0', STR_PAD_LEFT);
        $transactionStatus = Str::of((string) data_get($body, 'content.transactions.status', data_get($body, 'content.status', '')))
            ->lower()
            ->squish()
            ->toString();
        $message = (string) (
            data_get($body, 'response_description')
            ?? data_get($body, 'content.transactions.status')
            ?? data_get($body, 'content.errors')
            ?? data_get($body, 'message')
            ?? 'VTpass response received.'
        );

        if ($code === '000' && in_array($transactionStatus, ['delivered', 'success', 'successful', 'completed'], true)) {
            return $this->successful($message ?: 'VTpass delivered the service.', $body, $request);
        }

        if ($code === '000' && in_array($transactionStatus, ['initiated', 'pending'], true)) {
            return $this->pending($message ?: 'VTpass transaction is pending.', $body, $request);
        }

        if ($code === '000' && $transactionStatus === '') {
            return $this->pending($message ?: 'VTpass processed the request but did not return a final delivery status.', $body, $request);
        }

        if (in_array($code, self::PENDING_CODES, true)) {
            return $this->pending($message ?: 'VTpass transaction is still processing.', $body, $request);
        }

        if (in_array($code, self::FAILED_CODES, true)) {
            return $this->failed($message ?: 'VTpass reported a failed transaction.', $body, $request);
        }

        return $this->pending($message ?: 'VTpass returned an unclear response. Requery before retrying.', $body, $request);
    }

    private function successful(string $message, array $raw, array $request = []): array
    {
        return [
            'status' => BillPayment::STATUS_SUCCESSFUL,
            'message' => $message,
            'provider_reference' => $request['request_id'] ?? null,
            'token' => $this->extractToken($raw),
            'raw' => $raw,
            'request' => $request,
        ];
    }

    private function pending(string $message, array $raw, array $request = []): array
    {
        return [
            'status' => BillPayment::STATUS_PENDING,
            'message' => $message,
            'provider_reference' => $request['request_id'] ?? null,
            'token' => $this->extractToken($raw),
            'raw' => $raw,
            'request' => $request,
        ];
    }

    private function failed(string $message, array $raw, array $request = []): array
    {
        return [
            'status' => BillPayment::STATUS_FAILED,
            'message' => $message,
            'provider_reference' => $request['request_id'] ?? null,
            'token' => $this->extractToken($raw),
            'raw' => $raw,
            'request' => $request,
        ];
    }

    private function extractToken(array $raw): ?string
    {
        $token = data_get($raw, 'purchased_code')
            ?? data_get($raw, 'content.purchased_code')
            ?? data_get($raw, 'content.transactions.purchased_code')
            ?? data_get($raw, 'content.transactions.token')
            ?? data_get($raw, 'token');

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

    private function endpoint(Tenant $tenant, string $path): string
    {
        $settings = $this->settings->billPaymentSettings();
        $baseUrl = trim((string) ($settings['vtpass_base_url'] ?? ''));

        if ($baseUrl === '') {
            $baseUrl = ($settings['vtpass_environment'] ?? 'sandbox') === 'live'
                ? 'https://vtpass.com/api'
                : 'https://sandbox.vtpass.com/api';
        }

        return rtrim($baseUrl, '/').'/'.ltrim($path, '/');
    }

    private function postHeaders(Tenant $tenant): array
    {
        $settings = $this->settings->billPaymentSettings();

        return [
            'api-key' => (string) ($settings['vtpass_api_key'] ?? ''),
            'secret-key' => (string) ($settings['vtpass_secret_key'] ?? ''),
        ];
    }

    private function getHeaders(Tenant $tenant): array
    {
        $settings = $this->settings->billPaymentSettings();

        return [
            'api-key' => (string) ($settings['vtpass_api_key'] ?? ''),
            'public-key' => (string) ($settings['vtpass_public_key'] ?? ''),
        ];
    }
}
