<?php

namespace App\Services\Payments;

use App\Models\Organization;
use App\Models\PaymentGatewaySetting;
use App\Services\Care\PlanService;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class MerchantGatewayService
{
    public function __construct(
        protected PlanService $plans,
    ) {}

    public function settingFor(Organization $organization): ?PaymentGatewaySetting
    {
        return PaymentGatewaySetting::query()
            ->where('organization_id', $organization->id)
            ->first();
    }

    public function isConfigured(Organization $organization): bool
    {
        if (! $this->organizationMayUseGateway($organization)) {
            return false;
        }

        return (bool) $this->settingFor($organization)?->isConfigured();
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @return array{checkout_url: string, reference: string, provider: string}
     */
    public function initializeCheckout(
        Organization $organization,
        int $amountMinor,
        string $currency,
        string $email,
        string $callbackUrl,
        string $reference,
        array $metadata = [],
    ): array {
        $setting = $this->requireConfigured($organization);
        $currency = strtoupper($currency ?: 'GHS');

        return match ($setting->provider) {
            PaymentGatewaySetting::PROVIDER_PAYSTACK => $this->paystackInitialize($setting, $amountMinor, $currency, $email, $callbackUrl, $reference, $metadata),
            PaymentGatewaySetting::PROVIDER_FLUTTERWAVE => $this->flutterwaveInitialize($setting, $amountMinor, $currency, $email, $callbackUrl, $reference, $metadata),
            PaymentGatewaySetting::PROVIDER_HUBTEL => $this->hubtelInitialize($setting, $amountMinor, $currency, $email, $callbackUrl, $reference, $metadata),
            default => throw new RuntimeException('Unsupported payment provider.'),
        };
    }

    /**
     * @return array{paid: bool, amount_minor: int, reference: string, provider: string, raw: array<string, mixed>}
     */
    public function verify(Organization $organization, string $reference): array
    {
        $setting = $this->requireConfigured($organization);

        return match ($setting->provider) {
            PaymentGatewaySetting::PROVIDER_PAYSTACK => $this->paystackVerify($setting, $reference),
            PaymentGatewaySetting::PROVIDER_FLUTTERWAVE => $this->flutterwaveVerify($setting, $reference),
            PaymentGatewaySetting::PROVIDER_HUBTEL => $this->hubtelVerify($setting, $reference),
            default => throw new RuntimeException('Unsupported payment provider.'),
        };
    }

    protected function requireConfigured(Organization $organization): PaymentGatewaySetting
    {
        if (! $this->organizationMayUseGateway($organization)) {
            throw new RuntimeException('Connecting your own payment gateway requires Care Pro or Enterprise.');
        }

        $setting = $this->settingFor($organization);
        if (! $setting?->isConfigured()) {
            throw new RuntimeException('Connect Paystack, Flutterwave, or Hubtel in Integrations before collecting online payments.');
        }

        return $setting;
    }

    public function organizationMayUseGateway(Organization $organization): bool
    {
        return $this->plans->canUsePaymentGateway($organization);
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @return array{checkout_url: string, reference: string, provider: string}
     */
    protected function paystackInitialize(
        PaymentGatewaySetting $setting,
        int $amountMinor,
        string $currency,
        string $email,
        string $callbackUrl,
        string $reference,
        array $metadata,
    ): array {
        $response = Http::withToken((string) $setting->secret_key)
            ->acceptJson()
            ->timeout(20)
            ->post('https://api.paystack.co/transaction/initialize', [
                'email' => $email !== '' ? $email : 'payer@example.com',
                'amount' => $amountMinor,
                'currency' => $currency,
                'reference' => $reference,
                'callback_url' => $callbackUrl,
                'metadata' => $metadata,
            ]);

        if (! $response->successful() || ! ($response->json('status') ?? false)) {
            throw new RuntimeException($response->json('message') ?: 'Paystack could not start checkout.');
        }

        $url = (string) $response->json('data.authorization_url');
        if ($url === '') {
            throw new RuntimeException('Paystack did not return a checkout URL.');
        }

        return [
            'checkout_url' => $url,
            'reference' => (string) ($response->json('data.reference') ?: $reference),
            'provider' => PaymentGatewaySetting::PROVIDER_PAYSTACK,
        ];
    }

    /**
     * @return array{paid: bool, amount_minor: int, reference: string, provider: string, raw: array<string, mixed>}
     */
    protected function paystackVerify(PaymentGatewaySetting $setting, string $reference): array
    {
        $response = Http::withToken((string) $setting->secret_key)
            ->acceptJson()
            ->timeout(20)
            ->get('https://api.paystack.co/transaction/verify/'.rawurlencode($reference));

        $data = (array) ($response->json('data') ?? []);
        $paid = ($response->json('status') ?? false)
            && strtolower((string) ($data['status'] ?? '')) === 'success';

        return [
            'paid' => $paid,
            'amount_minor' => (int) ($data['amount'] ?? 0),
            'reference' => (string) ($data['reference'] ?? $reference),
            'provider' => PaymentGatewaySetting::PROVIDER_PAYSTACK,
            'raw' => $data,
        ];
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @return array{checkout_url: string, reference: string, provider: string}
     */
    protected function flutterwaveInitialize(
        PaymentGatewaySetting $setting,
        int $amountMinor,
        string $currency,
        string $email,
        string $callbackUrl,
        string $reference,
        array $metadata,
    ): array {
        $response = Http::withToken((string) $setting->secret_key)
            ->acceptJson()
            ->timeout(20)
            ->post('https://api.flutterwave.com/v3/payments', [
                'tx_ref' => $reference,
                'amount' => round($amountMinor / 100, 2),
                'currency' => $currency,
                'redirect_url' => $callbackUrl,
                'customer' => [
                    'email' => $email !== '' ? $email : 'payer@example.com',
                ],
                'customizations' => [
                    'title' => (string) ($metadata['title'] ?? 'Payment'),
                ],
                'meta' => $metadata,
            ]);

        if (! $response->successful() || ($response->json('status') ?? '') !== 'success') {
            throw new RuntimeException($response->json('message') ?: 'Flutterwave could not start checkout.');
        }

        $url = (string) $response->json('data.link');
        if ($url === '') {
            throw new RuntimeException('Flutterwave did not return a checkout URL.');
        }

        return [
            'checkout_url' => $url,
            'reference' => $reference,
            'provider' => PaymentGatewaySetting::PROVIDER_FLUTTERWAVE,
        ];
    }

    /**
     * @return array{paid: bool, amount_minor: int, reference: string, provider: string, raw: array<string, mixed>}
     */
    protected function flutterwaveVerify(PaymentGatewaySetting $setting, string $reference): array
    {
        $response = Http::withToken((string) $setting->secret_key)
            ->acceptJson()
            ->timeout(20)
            ->get('https://api.flutterwave.com/v3/transactions/verify_by_reference', [
                'tx_ref' => $reference,
            ]);

        $data = (array) ($response->json('data') ?? []);
        $paid = ($response->json('status') ?? '') === 'success'
            && strtolower((string) ($data['status'] ?? '')) === 'successful';
        $amountMajor = (float) ($data['amount'] ?? 0);

        return [
            'paid' => $paid,
            'amount_minor' => (int) round($amountMajor * 100),
            'reference' => (string) ($data['tx_ref'] ?? $reference),
            'provider' => PaymentGatewaySetting::PROVIDER_FLUTTERWAVE,
            'raw' => $data,
        ];
    }

    /**
     * @param  array<string, mixed>  $metadata
     * @return array{checkout_url: string, reference: string, provider: string}
     */
    protected function hubtelInitialize(
        PaymentGatewaySetting $setting,
        int $amountMinor,
        string $currency,
        string $email,
        string $callbackUrl,
        string $reference,
        array $metadata,
    ): array {
        // Hubtel: public_key = merchant account number, secret_key = API key (client secret),
        // webhook_secret optionally stores client id for Basic auth as "clientId:clientSecret".
        $auth = trim((string) ($setting->webhook_secret ?: $setting->secret_key));
        if (! str_contains($auth, ':')) {
            $auth = trim((string) $setting->public_key).':'.trim((string) $setting->secret_key);
        }

        $response = Http::withBasicAuth(...explode(':', $auth, 2))
            ->acceptJson()
            ->timeout(20)
            ->post('https://payproxyapi.hubtel.com/items/initiate', [
                'totalAmount' => round($amountMinor / 100, 2),
                'description' => (string) ($metadata['title'] ?? 'Payment'),
                'callbackUrl' => $callbackUrl,
                'returnUrl' => $callbackUrl,
                'merchantAccountNumber' => (string) $setting->public_key,
                'cancellationUrl' => $callbackUrl,
                'clientReference' => $reference,
            ]);

        if (! $response->successful()) {
            throw new RuntimeException($response->json('message') ?: 'Hubtel could not start checkout.');
        }

        $url = (string) ($response->json('data.checkoutUrl') ?? $response->json('data.checkoutDirectUrl') ?? '');
        if ($url === '') {
            throw new RuntimeException('Hubtel did not return a checkout URL.');
        }

        return [
            'checkout_url' => $url,
            'reference' => $reference,
            'provider' => PaymentGatewaySetting::PROVIDER_HUBTEL,
        ];
    }

    /**
     * @return array{paid: bool, amount_minor: int, reference: string, provider: string, raw: array<string, mixed>}
     */
    protected function hubtelVerify(PaymentGatewaySetting $setting, string $reference): array
    {
        $auth = trim((string) ($setting->webhook_secret ?: $setting->secret_key));
        if (! str_contains($auth, ':')) {
            $auth = trim((string) $setting->public_key).':'.trim((string) $setting->secret_key);
        }

        $response = Http::withBasicAuth(...explode(':', $auth, 2))
            ->acceptJson()
            ->timeout(20)
            ->get('https://api-txnstatus.hubtel.com/transactions/'.$setting->public_key.'/status', [
                'clientReference' => $reference,
            ]);

        $data = (array) ($response->json('data') ?? $response->json() ?? []);
        $status = strtolower((string) ($data['status'] ?? $data['transactionStatus'] ?? ''));
        $paid = in_array($status, ['success', 'successful', 'paid', 'completed'], true);
        $amountMajor = (float) ($data['amount'] ?? $data['totalAmount'] ?? 0);

        return [
            'paid' => $paid,
            'amount_minor' => (int) round($amountMajor * 100),
            'reference' => $reference,
            'provider' => PaymentGatewaySetting::PROVIDER_HUBTEL,
            'raw' => $data,
        ];
    }
}
