<?php

namespace App\Services;

use App\Models\PaymentLog;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

/**
 * PaymentProvider
 * 
 * Handles direct API communication with MTN MoMo APIs.
 * Provides methods for Collection, Disbursement, and transaction status checking.
 * Implements comprehensive logging for all API interactions.
 * 
 * @package PaymentConfigurationModule
 * @author SSHIA Development Team
 */
class PaymentProvider
{
    /**
     * Configuration service instance.
     *
     * @var PaymentConfigurationService
     */
    protected PaymentConfigurationService $configService;

    /**
     * API base URL.
     *
     * @var string
     */
    protected string $baseUrl;

    /**
     * Request timeout in seconds.
     *
     * @var int
     */
    protected int $timeout = 30;

    /**
     * Create a new PaymentProvider instance.
     *
     * @param PaymentConfigurationService $configService
     */
    public function __construct(PaymentConfigurationService $configService)
    {
        $this->configService = $configService;
        $this->baseUrl = $configService->getBaseUrl();
    }

    /**
     * Request an access token for Collection API.
     *
     * @return array{success: bool, token: string|null, expires_in: int|null, error: string|null}
     */
    public function requestCollectionToken(): array
    {
        $credentials = $this->configService->getCollectionCredentials();

        if (!$credentials) {
            return $this->errorResponse('Collection API credentials not configured');
        }

        return $this->requestToken(
            '/collection/token/',
            $credentials['api_user'],
            $credentials['api_key'],
            $credentials['subscription_key'],
            PaymentLog::TYPE_COLLECTION
        );
    }

    /**
     * Request an access token for Disbursement API.
     *
     * @return array{success: bool, token: string|null, expires_in: int|null, error: string|null}
     */
    public function requestDisbursementToken(): array
    {
        $credentials = $this->configService->getDisbursementCredentials();

        if (!$credentials) {
            return $this->errorResponse('Disbursement API credentials not configured');
        }

        return $this->requestToken(
            '/disbursement/token/',
            $credentials['api_user'],
            $credentials['api_key'],
            $credentials['subscription_key'],
            PaymentLog::TYPE_DISBURSEMENT
        );
    }

    /**
     * Request an access token from MTN MoMo API.
     *
     * @param string $endpoint
     * @param string $apiUser
     * @param string $apiKey
     * @param string $subscriptionKey
     * @param string $logType
     * @return array{success: bool, token: string|null, expires_in: int|null, error: string|null}
     */
    protected function requestToken(
        string $endpoint,
        string $apiUser,
        string $apiKey,
        string $subscriptionKey,
        string $logType
    ): array {
        $startTime = microtime(true);
        $url = $this->baseUrl . $endpoint;

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Basic ' . base64_encode($apiUser . ':' . $apiKey),
                'Ocp-Apim-Subscription-Key' => $subscriptionKey,
                'Content-Type' => 'application/json',
            ])
            ->timeout($this->timeout)
            ->post($url);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            // Log the request
            PaymentLog::log(
                type: $logType,
                endpoint: $url,
                requestPayload: ['grant_type' => 'client_credentials'],
                responseStatus: $response->status(),
                responseBody: $response->json(),
                executionTime: $executionTime,
                errorMessage: $response->failed() ? $response->body() : null
            );

            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'token' => $data['access_token'] ?? null,
                    'expires_in' => $data['expires_in'] ?? 3600,
                    'error' => null,
                ];
            }

            return $this->errorResponse(
                'Token request failed: ' . ($response->json()['message'] ?? $response->body())
            );

        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            PaymentLog::log(
                type: $logType,
                endpoint: $url,
                requestPayload: ['grant_type' => 'client_credentials'],
                responseStatus: null,
                responseBody: null,
                executionTime: $executionTime,
                errorMessage: $e->getMessage()
            );

            Log::error('Token request failed', [
                'endpoint' => $endpoint,
                'error' => $e->getMessage(),
            ]);

            return $this->errorResponse('Request failed: ' . $e->getMessage());
        }
    }

    /**
     * Make a collection request (request payment from customer).
     *
     * @param string $phoneNumber Customer phone number (MSISDN format)
     * @param float $amount Amount to collect
     * @param string $reference Unique transaction reference
     * @param string|null $payerMessage Optional message to display to payer
     * @param string|null $payeeNote Optional note for payee
     * @return array{success: bool, transaction_id: string|null, error: string|null}
     */
    public function makeCollectionRequest(
        string $phoneNumber,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        // Get access token
        $tokenResult = $this->requestCollectionToken();

        if (!$tokenResult['success']) {
            return $this->errorResponse($tokenResult['error']);
        }

        $credentials = $this->configService->getCollectionCredentials();
        $startTime = microtime(true);
        $url = $this->baseUrl . '/collection/v1_0/requesttopay';

        // Build headers — include X-Callback-Url only when configured
        $headers = [
            'Authorization'              => 'Bearer ' . $tokenResult['token'],
            'X-Reference-Id'             => $reference,
            'X-Target-Environment'       => $this->configService->getEnvironment(),
            'Ocp-Apim-Subscription-Key'  => $credentials['subscription_key'],
            'Content-Type'               => 'application/json',
        ];

        $callbackUrl = config('services.mtn_momo.callback_url');
        if (!empty($callbackUrl)) {
            $headers['X-Callback-Url'] = $callbackUrl;
        } else {
            Log::warning('MTN MoMo X-Callback-Url is not configured — callbacks will not be delivered', [
                'reference' => $reference,
            ]);
        }

        try {
            $response = Http::withHeaders($headers)
            ->timeout($this->timeout)
            ->post($url, [
                'amount'       => (string) $amount,
                'currency'     => $this->configService->getCurrency(),
                'externalId'   => $reference,
                'payer'        => [
                    'partyIdType' => 'MSISDN',
                    'partyId'     => $phoneNumber,
                ],
                'payerMessage' => $payerMessage ?? 'Payment request',
                'payeeNote'    => $payeeNote ?? 'Payment collection',
            ]);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            // Log the request
            PaymentLog::log(
                type: PaymentLog::TYPE_COLLECTION,
                endpoint: $url,
                requestPayload: [
                    'amount' => $amount,
                    'phone' => $phoneNumber,
                    'reference' => $reference,
                ],
                responseStatus: $response->status(),
                responseBody: $response->json(),
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $response->failed() ? $response->body() : null
            );

            if ($response->successful() || $response->status() === 202) {
                return [
                    'success' => true,
                    'transaction_id' => $reference,
                    'error' => null,
                ];
            }

            return $this->errorResponse(
                'Collection request failed: ' . ($response->json()['message'] ?? $response->body())
            );

        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            PaymentLog::log(
                type: PaymentLog::TYPE_COLLECTION,
                endpoint: $url,
                requestPayload: [
                    'amount' => $amount,
                    'phone' => $phoneNumber,
                    'reference' => $reference,
                ],
                responseStatus: null,
                responseBody: null,
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $e->getMessage()
            );

            Log::error('Collection request failed', [
                'reference' => $reference,
                'error' => $e->getMessage(),
            ]);

            return $this->errorResponse('Request failed: ' . $e->getMessage());
        }
    }

    /**
     * Make a card collection request via MTN MoMo Card Collection API.
     *
     * Uses the same /collection/v1_0/requesttopay endpoint as mobile money
     * but with partyIdType: CARD and an additional card object.
     *
     * @param string $cardNumber Card number (spaces will be stripped)
     * @param string $expiry Expiry date in MM/YY format
     * @param string $cvv CVV (3–4 digits)
     * @param float $amount Amount to collect
     * @param string $reference Unique transaction reference
     * @param string|null $payerMessage Optional message to display to payer
     * @param string|null $payeeNote Optional note for payee
     * @return array{success: bool, transaction_id: string|null, error: string|null}
     */
    public function makeCardCollectionRequest(
        string $cardNumber,
        string $expiry,
        string $cvv,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        $rawCard    = preg_replace('/\s+/', '', $cardNumber);
        $maskedCard = '****' . substr($rawCard, -4);

        Log::info("Card collection request: ref={$reference}, card={$maskedCard}, amount={$amount}");

        // Get access token
        $tokenResult = $this->requestCollectionToken();

        if (!$tokenResult['success']) {
            return $this->errorResponse($tokenResult['error']);
        }

        $credentials = $this->configService->getCollectionCredentials();
        $startTime   = microtime(true);
        $url         = $this->baseUrl . '/collection/v1_0/requesttopay';

        // Build headers — include X-Callback-Url only when configured
        $headers = [
            'Authorization'             => 'Bearer ' . $tokenResult['token'],
            'X-Reference-Id'            => $reference,
            'X-Target-Environment'      => $this->configService->getEnvironment(),
            'Ocp-Apim-Subscription-Key' => $credentials['subscription_key'],
            'Content-Type'              => 'application/json',
        ];

        $callbackUrl = config('services.mtn_momo.callback_url');
        if (!empty($callbackUrl)) {
            $headers['X-Callback-Url'] = $callbackUrl;
        } else {
            Log::warning('MTN MoMo X-Callback-Url is not configured — callbacks will not be delivered', [
                'reference' => $reference,
            ]);
        }

        $body = [
            'amount'       => (string) $amount,
            'currency'     => $this->configService->getCurrency(),
            'externalId'   => $reference,
            'payer'        => [
                'partyIdType' => 'CARD',
                'partyId'     => $rawCard,
            ],
            'payerMessage' => $payerMessage ?? 'Card payment request',
            'payeeNote'    => $payeeNote ?? 'Card collection',
            'card'         => [
                'number' => $rawCard,
                'expiry' => $expiry,
                'cvv'    => $cvv,
            ],
        ];

        try {
            $response = Http::withHeaders($headers)
                ->timeout($this->timeout)
                ->post($url, $body);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            // Log the request — mask card number
            PaymentLog::log(
                type: PaymentLog::TYPE_COLLECTION,
                endpoint: $url,
                requestPayload: [
                    'amount'    => $amount,
                    'card'      => $maskedCard,
                    'reference' => $reference,
                    'method'    => 'card',
                ],
                responseStatus: $response->status(),
                responseBody: $response->json(),
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $response->failed() ? $response->body() : null
            );

            if ($response->successful() || $response->status() === 202) {
                return [
                    'success'        => true,
                    'transaction_id' => $reference,
                    'error'          => null,
                ];
            }

            return $this->errorResponse(
                'Card collection request failed: ' . ($response->json()['message'] ?? $response->body())
            );

        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            PaymentLog::log(
                type: PaymentLog::TYPE_COLLECTION,
                endpoint: $url,
                requestPayload: [
                    'amount'    => $amount,
                    'card'      => $maskedCard,
                    'reference' => $reference,
                    'method'    => 'card',
                ],
                responseStatus: null,
                responseBody: null,
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $e->getMessage()
            );

            Log::error('Card collection request failed', [
                'reference' => $reference,
                'error'     => $e->getMessage(),
            ]);

            return $this->errorResponse('Request failed: ' . $e->getMessage());
        }
    }

    /**
     * Make a disbursement request (send money to customer).
     *
     * @param string $phoneNumber Recipient phone number (MSISDN format)
     * @param float $amount Amount to disburse
     * @param string $reference Unique transaction reference
     * @param string|null $payerMessage Optional message
     * @param string|null $payeeNote Optional note
     * @return array{success: bool, transaction_id: string|null, error: string|null}
     */
    public function makeDisbursementRequest(
        string $phoneNumber,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        // Get access token
        $tokenResult = $this->requestDisbursementToken();

        if (!$tokenResult['success']) {
            return $this->errorResponse($tokenResult['error']);
        }

        $credentials = $this->configService->getDisbursementCredentials();
        $startTime = microtime(true);
        $url = $this->baseUrl . '/disbursement/v1_0/transfer';

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $tokenResult['token'],
                'X-Reference-Id' => $reference,
                'X-Target-Environment' => $this->configService->getEnvironment(),
                'Ocp-Apim-Subscription-Key' => $credentials['subscription_key'],
                'Content-Type' => 'application/json',
            ])
            ->timeout($this->timeout)
            ->post($url, [
                'amount' => (string) $amount,
                'currency' => $this->configService->getCurrency(),
                'externalId' => $reference,
                'payee' => [
                    'partyIdType' => 'MSISDN',
                    'partyId' => $phoneNumber,
                ],
                'payerMessage' => $payerMessage ?? 'Disbursement',
                'payeeNote' => $payeeNote ?? 'Money transfer',
            ]);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            // Log the request
            PaymentLog::log(
                type: PaymentLog::TYPE_DISBURSEMENT,
                endpoint: $url,
                requestPayload: [
                    'amount' => $amount,
                    'phone' => $phoneNumber,
                    'reference' => $reference,
                ],
                responseStatus: $response->status(),
                responseBody: $response->json(),
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $response->failed() ? $response->body() : null
            );

            if ($response->successful() || $response->status() === 202) {
                return [
                    'success' => true,
                    'transaction_id' => $reference,
                    'error' => null,
                ];
            }

            return $this->errorResponse(
                'Disbursement request failed: ' . ($response->json()['message'] ?? $response->body())
            );

        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            PaymentLog::log(
                type: PaymentLog::TYPE_DISBURSEMENT,
                endpoint: $url,
                requestPayload: [
                    'amount' => $amount,
                    'phone' => $phoneNumber,
                    'reference' => $reference,
                ],
                responseStatus: null,
                responseBody: null,
                transactionReference: $reference,
                executionTime: $executionTime,
                errorMessage: $e->getMessage()
            );

            Log::error('Disbursement request failed', [
                'reference' => $reference,
                'error' => $e->getMessage(),
            ]);

            return $this->errorResponse('Request failed: ' . $e->getMessage());
        }
    }

    /**
     * Check transaction status.
     *
     * @param string $transactionId Transaction reference ID
     * @param string $type 'collection' or 'disbursement'
     * @return array{success: bool, status: string|null, data: array|null, error: string|null}
     */
    public function checkTransactionStatus(string $transactionId, string $type = 'collection'): array
    {
        $startTime = microtime(true);

        // Get token based on type
        $tokenResult = $type === 'collection' 
            ? $this->requestCollectionToken() 
            : $this->requestDisbursementToken();

        if (!$tokenResult['success']) {
            return $this->errorResponse($tokenResult['error']);
        }

        $credentials = $type === 'collection' 
            ? $this->configService->getCollectionCredentials() 
            : $this->configService->getDisbursementCredentials();

        $endpoint = $type === 'collection'
            ? "/collection/v1_0/requesttopay/{$transactionId}"
            : "/disbursement/v1_0/transfer/{$transactionId}";

        $url = $this->baseUrl . $endpoint;

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $tokenResult['token'],
                'X-Target-Environment' => $this->configService->getEnvironment(),
                'Ocp-Apim-Subscription-Key' => $credentials['subscription_key'],
                'Content-Type' => 'application/json',
            ])
            ->timeout($this->timeout)
            ->get($url);

            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            // Log the request
            PaymentLog::log(
                type: PaymentLog::TYPE_STATUS_CHECK,
                endpoint: $url,
                requestPayload: ['transaction_id' => $transactionId],
                responseStatus: $response->status(),
                responseBody: $response->json(),
                transactionReference: $transactionId,
                executionTime: $executionTime,
                errorMessage: $response->failed() ? $response->body() : null
            );

            if ($response->successful()) {
                $data = $response->json();
                return [
                    'success' => true,
                    'status' => $data['status'] ?? 'UNKNOWN',
                    'data' => $data,
                    'error' => null,
                ];
            }

            return $this->errorResponse(
                'Status check failed: ' . ($response->json()['message'] ?? $response->body())
            );

        } catch (\Exception $e) {
            $executionTime = (int) ((microtime(true) - $startTime) * 1000);

            PaymentLog::log(
                type: PaymentLog::TYPE_STATUS_CHECK,
                endpoint: $url,
                requestPayload: ['transaction_id' => $transactionId],
                responseStatus: null,
                responseBody: null,
                transactionReference: $transactionId,
                executionTime: $executionTime,
                errorMessage: $e->getMessage()
            );

            Log::error('Transaction status check failed', [
                'transaction_id' => $transactionId,
                'error' => $e->getMessage(),
            ]);

            return $this->errorResponse('Request failed: ' . $e->getMessage());
        }
    }

    /**
     * Test API connection.
     *
     * @param string $type 'collection' or 'disbursement'
     * @return array{success: bool, message: string, details: array|null}
     */
    public function testConnection(string $type = 'collection'): array
    {
        $startTime = microtime(true);

        $tokenResult = $type === 'collection' 
            ? $this->requestCollectionToken() 
            : $this->requestDisbursementToken();

        $executionTime = (int) ((microtime(true) - $startTime) * 1000);

        // Log test attempt
        PaymentLog::log(
            type: PaymentLog::TYPE_TEST,
            endpoint: $this->baseUrl . "/{$type}/token/",
            requestPayload: ['test' => true, 'type' => $type],
            responseStatus: $tokenResult['success'] ? 200 : 401,
            responseBody: $tokenResult,
            executionTime: $executionTime,
            errorMessage: $tokenResult['success'] ? null : $tokenResult['error']
        );

        if ($tokenResult['success']) {
            return [
                'success' => true,
                'message' => ucfirst($type) . ' API connection successful',
                'details' => [
                    'environment' => $this->configService->getEnvironment(),
                    'token_expires_in' => $tokenResult['expires_in'],
                    'execution_time_ms' => $executionTime,
                ],
            ];
        }

        return [
            'success' => false,
            'message' => ucfirst($type) . ' API connection failed',
            'details' => [
                'error' => $tokenResult['error'],
                'execution_time_ms' => $executionTime,
            ],
        ];
    }

    /**
     * Generate a unique transaction reference.
     *
     * @return string
     */
    public function generateReference(): string
    {
        return (string) \Illuminate\Support\Str::uuid();
    }

    /**
     * Create a standardized error response.
     *
     * @param string $message
     * @return array{success: bool, token: string|null, expires_in: int|null, error: string|null, message: string|null, status: string|null, data: array|null, transaction_id: string|null, details: array|null}
     */
    protected function errorResponse(string $message): array
    {
        return [
            'success' => false,
            'token' => null,
            'expires_in' => null,
            'error' => $message,
            'message' => $message,
            'status' => null,
            'data' => null,
            'transaction_id' => null,
            'details' => null,
        ];
    }
}
