<?php

namespace App\Services;

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

/**
 * PaymentService
 * 
 * Public service for other modules to process payments via MTN MoMo.
 * Provides a clean, standardized interface for collection, disbursement, and status checking.
 * 
 * Usage Example:
 * ```php
 * $paymentService = app(PaymentService::class);
 * 
 * // Request payment from customer
 * $result = $paymentService->initiateCollection(
 *     phoneNumber: '256771234567',
 *     amount: 5000,
 *     reference: 'INV-001'
 * );
 * 
 * if ($result['success']) {
 *     echo "Transaction ID: " . $result['transaction_id'];
 * }
 * ```
 * 
 * @package PaymentConfigurationModule
 * @author SSHIA Development Team
 */
class PaymentService
{
    /**
     * Payment provider instance.
     *
     * @var PaymentProvider
     */
    protected PaymentProvider $provider;

    /**
     * Configuration service instance.
     *
     * @var PaymentConfigurationService
     */
    protected PaymentConfigurationService $configService;

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

    /**
     * Initiate a collection request (request payment from customer).
     * 
     * Sends a payment request to the customer's mobile money account.
     * The customer will receive a prompt on their phone to confirm the payment.
     *
     * @param string $phoneNumber Customer phone number in MSISDN format (e.g., 256771234567)
     * @param float $amount Amount to collect (in smallest currency unit or as per API requirements)
     * @param string $reference Unique reference for this transaction
     * @param string|null $payerMessage Optional message to display to the payer
     * @param string|null $payeeNote Optional note for the payee
     * @return array{success: bool, transaction_id: string|null, message: string, data: array|null}
     */
    public function initiateCollection(
        string $phoneNumber,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        // Validate inputs
        if (!$this->validatePhoneNumber($phoneNumber)) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid phone number format. Use MSISDN format (e.g., 256771234567)',
                transactionId: null,
                data: null
            );
        }

        if ($amount <= 0) {
            return $this->formatResponse(
                success: false,
                message: 'Amount must be greater than zero',
                transactionId: null,
                data: null
            );
        }

        // Check if collection is configured
        if (!$this->configService->hasCollectionCredentials()) {
            return $this->formatResponse(
                success: false,
                message: 'Collection API is not configured. Please configure credentials first.',
                transactionId: null,
                data: null
            );
        }

        // Generate reference if not provided
        if (empty($reference)) {
            $reference = $this->provider->generateReference();
        }

        Log::info('Initiating collection request', [
            'phone' => $this->maskPhoneNumber($phoneNumber),
            'amount' => $amount,
            'reference' => $reference,
        ]);

        // Make the collection request
        $result = $this->provider->makeCollectionRequest(
            phoneNumber: $phoneNumber,
            amount: $amount,
            reference: $reference,
            payerMessage: $payerMessage,
            payeeNote: $payeeNote
        );

        return $this->formatResponse(
            success: $result['success'],
            message: $result['success'] 
                ? 'Collection request initiated successfully' 
                : ($result['error'] ?? 'Collection request failed'),
            transactionId: $result['transaction_id'] ?? $reference,
            data: [
                'reference' => $reference,
                'amount' => $amount,
                'phone' => $this->maskPhoneNumber($phoneNumber),
                'environment' => $this->configService->getEnvironment(),
            ]
        );
    }

    /**
     * Initiate a card collection request via MTN MoMo Card Collection API.
     *
     * Validates card fields, then delegates to PaymentProvider::makeCardCollectionRequest.
     *
     * @param string $cardNumber Card number (spaces allowed — stripped internally)
     * @param string $expiry Expiry 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
     * @param string|null $payeeNote Optional note
     * @return array{success: bool, transaction_id: string|null, message: string, data: array|null}
     */
    public function initiateCardCollection(
        string $cardNumber,
        string $expiry,
        string $cvv,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        // Strip spaces from card number
        $rawCard = preg_replace('/\s+/', '', $cardNumber);

        // Validate card number: 13–19 digits
        if (!preg_match('/^\d{13,19}$/', $rawCard)) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid card number. Must be 13–19 digits.',
                transactionId: null,
                data: null
            );
        }

        // Validate expiry format MM/YY
        if (!preg_match('/^(0[1-9]|1[0-2])\/\d{2}$/', $expiry)) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid expiry format. Use MM/YY (e.g. 12/26).',
                transactionId: null,
                data: null
            );
        }

        // Check expiry is not in the past
        [$month, $year] = explode('/', $expiry);
        $expiryDate = \Carbon\Carbon::createFromDate(2000 + (int) $year, (int) $month, 1)->endOfMonth();
        if ($expiryDate->isPast()) {
            return $this->formatResponse(
                success: false,
                message: 'Card has expired.',
                transactionId: null,
                data: null
            );
        }

        // Validate CVV: 3–4 digits
        if (!preg_match('/^\d{3,4}$/', $cvv)) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid CVV. Must be 3–4 digits.',
                transactionId: null,
                data: null
            );
        }

        if ($amount <= 0) {
            return $this->formatResponse(
                success: false,
                message: 'Amount must be greater than zero.',
                transactionId: null,
                data: null
            );
        }

        if (!$this->configService->hasCollectionCredentials()) {
            return $this->formatResponse(
                success: false,
                message: 'Collection API is not configured. Please configure credentials first.',
                transactionId: null,
                data: null
            );
        }

        if (empty($reference)) {
            $reference = $this->provider->generateReference();
        }

        Log::info('Initiating card collection request', [
            'card'      => '****' . substr($rawCard, -4),
            'amount'    => $amount,
            'reference' => $reference,
        ]);

        $result = $this->provider->makeCardCollectionRequest(
            cardNumber: $rawCard,
            expiry: $expiry,
            cvv: $cvv,
            amount: $amount,
            reference: $reference,
            payerMessage: $payerMessage,
            payeeNote: $payeeNote
        );

        return $this->formatResponse(
            success: $result['success'],
            message: $result['success']
                ? 'Card collection request initiated successfully'
                : ($result['error'] ?? 'Card collection request failed'),
            transactionId: $result['transaction_id'] ?? $reference,
            data: [
                'reference'   => $reference,
                'amount'      => $amount,
                'card'        => '****' . substr($rawCard, -4),
                'environment' => $this->configService->getEnvironment(),
            ]
        );
    }

    /**
     * Initiate a disbursement request (send money to customer).
     * 
     * Sends money directly to the customer's mobile money account.
     *
     * @param string $phoneNumber Recipient phone number in MSISDN format
     * @param float $amount Amount to disburse
     * @param string $reference Unique reference for this transaction
     * @param string|null $payerMessage Optional message
     * @param string|null $payeeNote Optional note
     * @return array{success: bool, transaction_id: string|null, message: string, data: array|null}
     */
    public function initiateDisbursement(
        string $phoneNumber,
        float $amount,
        string $reference,
        ?string $payerMessage = null,
        ?string $payeeNote = null
    ): array {
        // Validate inputs
        if (!$this->validatePhoneNumber($phoneNumber)) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid phone number format. Use MSISDN format (e.g., 256771234567)',
                transactionId: null,
                data: null
            );
        }

        if ($amount <= 0) {
            return $this->formatResponse(
                success: false,
                message: 'Amount must be greater than zero',
                transactionId: null,
                data: null
            );
        }

        // Check if disbursement is configured
        if (!$this->configService->hasDisbursementCredentials()) {
            return $this->formatResponse(
                success: false,
                message: 'Disbursement API is not configured. Please configure credentials first.',
                transactionId: null,
                data: null
            );
        }

        // Generate reference if not provided
        if (empty($reference)) {
            $reference = $this->provider->generateReference();
        }

        Log::info('Initiating disbursement request', [
            'phone' => $this->maskPhoneNumber($phoneNumber),
            'amount' => $amount,
            'reference' => $reference,
        ]);

        // Make the disbursement request
        $result = $this->provider->makeDisbursementRequest(
            phoneNumber: $phoneNumber,
            amount: $amount,
            reference: $reference,
            payerMessage: $payerMessage,
            payeeNote: $payeeNote
        );

        return $this->formatResponse(
            success: $result['success'],
            message: $result['success'] 
                ? 'Disbursement initiated successfully' 
                : ($result['error'] ?? 'Disbursement failed'),
            transactionId: $result['transaction_id'] ?? $reference,
            data: [
                'reference' => $reference,
                'amount' => $amount,
                'phone' => $this->maskPhoneNumber($phoneNumber),
                'environment' => $this->configService->getEnvironment(),
            ]
        );
    }

    /**
     * Check the status of a transaction.
     *
     * @param string $transactionId The transaction reference ID
     * @param string $type 'collection' or 'disbursement'
     * @return array{success: bool, transaction_id: string|null, message: string, data: array|null, status: string|null}
     */
    public function checkTransactionStatus(
        string $transactionId,
        string $type = 'collection'
    ): array {
        if (empty($transactionId)) {
            return $this->formatResponse(
                success: false,
                message: 'Transaction ID is required',
                transactionId: null,
                data: null
            );
        }

        if (!in_array($type, ['collection', 'disbursement'])) {
            return $this->formatResponse(
                success: false,
                message: 'Invalid type. Must be "collection" or "disbursement"',
                transactionId: null,
                data: null
            );
        }

        Log::info('Checking transaction status', [
            'transaction_id' => $transactionId,
            'type' => $type,
        ]);

        $result = $this->provider->checkTransactionStatus($transactionId, $type);

        return $this->formatResponse(
            success: $result['success'],
            message: $result['success'] 
                ? 'Transaction status retrieved' 
                : ($result['error'] ?? 'Failed to check status'),
            transactionId: $transactionId,
            data: $result['data'],
            status: $result['status']
        );
    }

    /**
     * Generate a unique transaction reference.
     *
     * @return string
     */
    public function generateReference(): string
    {
        return $this->provider->generateReference();
    }

    /**
     * Check if the payment service is ready.
     * 
     * Returns true if at least one API type is configured.
     *
     * @return bool
     */
    public function isReady(): bool
    {
        return $this->configService->isConfigured();
    }

    /**
     * Check if collection is available.
     *
     * @return bool
     */
    public function canCollect(): bool
    {
        return $this->configService->hasCollectionCredentials();
    }

    /**
     * Check if disbursement is available.
     *
     * @return bool
     */
    public function canDisburse(): bool
    {
        return $this->configService->hasDisbursementCredentials();
    }

    /**
     * Get service status and capabilities.
     *
     * @return array<string, mixed>
     */
    public function getStatus(): array
    {
        return [
            'ready' => $this->isReady(),
            'environment' => $this->configService->getEnvironment(),
            'collection_available' => $this->canCollect(),
            'disbursement_available' => $this->canDisburse(),
        ];
    }

    /**
     * Validate phone number format.
     * 
     * Validates MSISDN format without leading + or 00.
     *
     * @param string $phoneNumber
     * @return bool
     */
    protected function validatePhoneNumber(string $phoneNumber): bool
    {
        // Remove any whitespace
        $phone = preg_replace('/\s/', '', $phoneNumber);

        // Check if it matches MSISDN format (numeric, reasonable length)
        return preg_match('/^[1-9]\d{8,14}$/', $phone) === 1;
    }

    /**
     * Mask phone number for logging/display.
     *
     * @param string $phoneNumber
     * @return string
     */
    protected function maskPhoneNumber(string $phoneNumber): string
    {
        $length = strlen($phoneNumber);
        if ($length <= 6) {
            return str_repeat('*', $length);
        }

        return substr($phoneNumber, 0, 3) . '****' . substr($phoneNumber, -3);
    }

    /**
     * Format a standardized response.
     *
     * @param bool $success
     * @param string $message
     * @param string|null $transactionId
     * @param array|null $data
     * @param string|null $status
     * @return array{success: bool, transaction_id: string|null, message: string, data: array|null, status: string|null}
     */
    protected function formatResponse(
        bool $success,
        string $message,
        ?string $transactionId,
        ?array $data,
        ?string $status = null
    ): array {
        return [
            'success' => $success,
            'transaction_id' => $transactionId,
            'message' => $message,
            'data' => $data,
            'status' => $status,
        ];
    }
}
