<?php

namespace App\Services\Payments;

use App\Exceptions\Payments\MonnifyException;
use App\Services\SystemSettingService;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class MonnifyService
{
    public function __construct(
        protected SystemSettingService $systemSettingService
    ) {
    }

    public function isConfigured(): bool
    {
        return filled($this->setting('base_url'))
            && filled($this->setting('api_key'))
            && filled($this->setting('secret_key'))
            && filled($this->setting('contract_code'));
    }

    public function createReservedAccount(array $payload): array
    {
        $this->assertConfigured();

        $normalizedPayload = array_filter([
            'accountReference' => (string) ($payload['accountReference'] ?? ''),
            'accountName' => (string) ($payload['accountName'] ?? ''),
            'currencyCode' => (string) ($payload['currencyCode'] ?? 'NGN'),
            'contractCode' => (string) $this->setting('contract_code'),
            'customerName' => (string) ($payload['customerName'] ?? ''),
            'customerEmail' => (string) ($payload['customerEmail'] ?? ''),
            'bvn' => $payload['bvn'] ?? null,
            'nin' => $payload['nin'] ?? null,
            'getAllAvailableBanks' => (bool) ($payload['getAllAvailableBanks'] ?? false),
            'preferredBanks' => array_values(array_filter((array) ($payload['preferredBanks'] ?? []))),
        ], fn ($value) => $value !== null && $value !== '');

        $response = $this->client()
            ->withToken($this->accessToken())
            ->post('/api/v2/bank-transfer/reserved-accounts', $normalizedPayload);

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

        if (! $response->successful()) {
            Log::warning('Monnify reserved account provisioning failed', [
                'status' => $response->status(),
                'body' => $body,
                'payload' => $normalizedPayload,
            ]);

            throw new MonnifyException(
                (string) (data_get($body, 'responseMessage')
                    ?? data_get($body, 'message')
                    ?? 'Monnify reserved account provisioning failed.')
            );
        }

        return [
            'success' => true,
            'message' => (string) (data_get($body, 'responseMessage') ?? 'Reserved account provisioned successfully.'),
            'response' => $body,
            'accounts' => (array) (data_get($body, 'responseBody.accounts') ?? []),
            'account_reference' => (string) (data_get($body, 'responseBody.accountReference')
                ?? $normalizedPayload['accountReference']
                ?? ''),
        ];
    }

    public function initializeInstantCheckout(array $payload): array
    {
        $this->assertConfigured();

        $paymentReference = (string) ($payload['paymentReference'] ?? '');
        $amount = (float) ($payload['amount'] ?? 0);
        $customerName = (string) ($payload['customerName'] ?? '');
        $customerEmail = (string) ($payload['customerEmail'] ?? '');
        $redirectUrl = (string) ($payload['redirectUrl'] ?? '');

        if (
            $paymentReference === ''
            || $amount <= 0
            || $customerName === ''
            || $customerEmail === ''
            || $redirectUrl === ''
        ) {
            throw new MonnifyException('Invalid Monnify instant checkout payload.');
        }

        $requestBody = [
            'amount' => round($amount, 2),
            'customerName' => $customerName,
            'customerEmail' => $customerEmail,
            'paymentReference' => $paymentReference,
            'paymentDescription' => (string) ($payload['paymentDescription'] ?? 'Wallet funding'),
            'currencyCode' => (string) ($payload['currencyCode'] ?? 'NGN'),
            'contractCode' => (string) $this->setting('contract_code'),
            'redirectUrl' => $redirectUrl,
            'paymentMethods' => array_values(array_filter((array) ($payload['paymentMethods'] ?? ['CARD', 'ACCOUNT_TRANSFER']))),
            'metaData' => (array) ($payload['metaData'] ?? []),
        ];

        $response = $this->client()
            ->withToken($this->accessToken())
            ->post('/api/v1/merchant/transactions/init-transaction', $requestBody);

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

        if (! $response->successful()) {
            throw new MonnifyException(
                (string) (data_get($body, 'responseMessage')
                    ?? data_get($body, 'message')
                    ?? 'Unable to initialize Monnify instant checkout.')
            );
        }

        $checkoutUrl = (string) (data_get($body, 'responseBody.checkoutUrl')
            ?? data_get($body, 'responseBody.paymentLink')
            ?? data_get($body, 'responseBody.authorizationUrl')
            ?? '');

        if ($checkoutUrl === '') {
            throw new MonnifyException('Monnify checkout URL is missing from response.');
        }

        return [
            'success' => true,
            'message' => (string) (data_get($body, 'responseMessage') ?? 'Monnify checkout initialized.'),
            'checkout_url' => $checkoutUrl,
            'payment_reference' => (string) (data_get($body, 'responseBody.paymentReference') ?? $paymentReference),
            'response' => $body,
        ];
    }

    public function accessToken(): string
    {
        $this->assertConfigured();

        return Cache::remember(
            $this->cacheKey(),
            now()->addMinutes(45),
            function () {
                $credentials = base64_encode(
                    $this->setting('api_key').':'.$this->setting('secret_key')
                );

                $response = $this->client()
                    ->withHeaders([
                        'Authorization' => 'Basic '.$credentials,
                    ])
                    ->post('/api/v1/auth/login');

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

                if (! $response->successful()) {
                    throw new MonnifyException(
                        (string) (data_get($body, 'responseMessage')
                            ?? data_get($body, 'message')
                            ?? 'Unable to authenticate with Monnify.')
                    );
                }

                $token = (string) (data_get($body, 'responseBody.accessToken') ?? '');

                if ($token === '') {
                    throw new MonnifyException('Monnify access token missing from response.');
                }

                return $token;
            }
        );
    }

    protected function client(): PendingRequest
    {
        return Http::acceptJson()
            ->contentType('application/json')
            ->timeout((int) $this->setting('timeout', 30))
            ->baseUrl(rtrim((string) $this->setting('base_url'), '/'));
    }

    protected function assertConfigured(): void
    {
        if (! $this->isConfigured()) {
            throw new MonnifyException('Monnify credentials are not configured. Set them in Admin Integration Settings or MONNIFY_* environment values.');
        }
    }

    protected function cacheKey(): string
    {
        return 'payments:monnify:access_token:'.md5((string) $this->setting('api_key'));
    }

    protected function setting(string $key, mixed $default = null): mixed
    {
        $fallback = match ($key) {
            'base_url' => config('services.monnify.base_url'),
            'api_key' => config('services.monnify.api_key'),
            'secret_key' => config('services.monnify.secret_key'),
            'contract_code' => config('services.monnify.contract_code'),
            'timeout' => config('services.monnify.timeout', 30),
            default => $default,
        };

        $value = $this->systemSettingService->get('payments.monnify', $key, $fallback);

        if (is_string($value) && trim($value) === '') {
            return $fallback ?? $default;
        }

        return $value ?? $default ?? $fallback;
    }
}
