<?php

namespace Modules\AuthorizeNetGateway\Services;

use Illuminate\Support\Facades\Log;
use net\authorize\api\contract\v1 as AnetAPI;
use net\authorize\api\controller as AnetController;

class AuthorizeNetService
{
    protected $merchantAuthentication;
    protected $endpoint;

    public function __construct()
    {
        if (!function_exists('setting')) {
            throw new \Exception("Settings helper function not found. Ensure it's globally available.");
        }

        $loginId = setting('authorizenet_login_id');
        $transactionKey = setting('authorizenet_transaction_key');
        $mode = setting('authorizenet_mode', 'sandbox');

        if (!$loginId || !$transactionKey) {
            Log::warning('Authorize.Net API Login ID or Transaction Key is not configured.');
            // Throw an exception to prevent using an uninitialized service.
            throw new \RuntimeException('Authorize.Net API Login ID or Transaction Key is not configured.');
        }

        $this->merchantAuthentication = new AnetAPI\MerchantAuthenticationType();
        $this->merchantAuthentication->setName($loginId);
        $this->merchantAuthentication->setTransactionKey($transactionKey);

        if ($mode === 'live') {
            $this->endpoint = \net\authorize\api\constants\ANetEnvironment::PRODUCTION;
        } else {
            $this->endpoint = \net\authorize\api\constants\ANetEnvironment::SANDBOX;
        }
    }

    public function getMerchantAuthentication()
    {
        return $this->merchantAuthentication;
    }

    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Charge a credit card.
     *
     * @param float $amount
     * @param array $opaqueDataOrCardDetails // If using Accept.js: ['dataDescriptor', 'dataValue']. If direct: ['cardNumber', 'expirationDate', 'cardCode']
     * @param array $customerDetails ['email', 'firstName', 'lastName', 'address', 'city', 'state', 'zip', 'country']
     * @param string $invoiceNumber Optional invoice number
     * @param string $description Optional description
     * @return AnetAPI\CreateTransactionResponse
     */
    public function chargeCreditCard(float $amount, array $opaqueDataOrCardDetails, array $customerDetails, string $invoiceNumber = null, string $description = null)
    {
        if (!$this->merchantAuthentication) {
            Log::error('Authorize.Net service not initialized properly. Merchant authentication missing.');
            // Create a mock error response or throw an exception
            $errorResponse = new AnetAPI\CreateTransactionResponse();
            $messages = new AnetAPI\MessagesType();
            $message = new AnetAPI\MessageType();
            $message->setCode("E00000"); // Generic error
            $message->setText("Authorize.Net service not configured.");
            $messages->setMessage([$message]);
            $errorResponse->setMessages($messages);
            return $errorResponse;
        }

        // Add the payment data to a paymentType object
        $paymentOne = new AnetAPI\PaymentType();

        // Check if we are using OpaqueData (Accept.js nonce) or direct card details
        if (isset($opaqueDataOrCardDetails['dataDescriptor']) && isset($opaqueDataOrCardDetails['dataValue'])) {
            $opaqueData = new AnetAPI\OpaqueDataType();
            $opaqueData->setDataDescriptor($opaqueDataOrCardDetails['dataDescriptor']);
            $opaqueData->setDataValue($opaqueDataOrCardDetails['dataValue']);
            $paymentOne->setOpaqueData($opaqueData);
        } else { // Fallback or direct card details (less secure, for previous compatibility if needed)
            $creditCard = new AnetAPI\CreditCardType();
            $creditCard->setCardNumber($opaqueDataOrCardDetails['cardNumber']);
            $creditCard->setExpirationDate($opaqueDataOrCardDetails['expirationDate']); // YYYY-MM
            $creditCard->setCardCode($opaqueDataOrCardDetails['cardCode'] ?? null);
            $paymentOne->setCreditCard($creditCard);
            Log::warning('AuthorizeNetService: Processing payment with direct card details. Consider Accept.js for PCI compliance.');
        }

        // Create order information if provided
        $order = null;
        if ($invoiceNumber || $description) {
            $order = new AnetAPI\OrderType();
            if ($invoiceNumber) {
                $order->setInvoiceNumber($invoiceNumber);
            }
            if ($description) {
                $order->setDescription($description);
            }
        }

        // Set the customer's Bill To address
        $customerAddress = new AnetAPI\CustomerAddressType();
        $customerAddress->setFirstName($customerDetails['firstName'] ?? null);
        $customerAddress->setLastName($customerDetails['lastName'] ?? null);
        $customerAddress->setAddress($customerDetails['address'] ?? null);
        $customerAddress->setCity($customerDetails['city'] ?? null);
        $customerAddress->setState($customerDetails['state'] ?? null);
        $customerAddress->setZip($customerDetails['zip'] ?? null);
        $customerAddress->setCountry($customerDetails['country'] ?? 'USA'); // Default or make required

        // Set the customer's email
        $customerData = new AnetAPI\CustomerDataType();
        $customerData->setEmail($customerDetails['email'] ?? null);

        // Create a transaction
        $transactionRequestType = new AnetAPI\TransactionRequestType();
        $transactionRequestType->setTransactionType("authCaptureTransaction"); // Or "authOnlyTransaction"
        $transactionRequestType->setAmount($amount);
        $transactionRequestType->setPayment($paymentOne);
        if ($order) {
            $transactionRequestType->setOrder($order);
        }
        $transactionRequestType->setBillTo($customerAddress);
        $transactionRequestType->setCustomer($customerData);

        $request = new AnetAPI\CreateTransactionRequest();
        $request->setMerchantAuthentication($this->getMerchantAuthentication());
        $request->setTransactionRequest($transactionRequestType);

        $controller = new AnetController\CreateTransactionController($request);
        return $controller->executeWithApiResponse($this->getEndpoint());
    }

    /**
     * Extracts and formats error messages from an Authorize.Net API response.
     *
     * @param AnetAPI\ANetApiResponseType|null $response The API response object.
     * @return string A formatted string of error messages.
     */
    public function getErrorMessage(AnetAPI\ANetApiResponseType $response = null): string
    {
        $errorMessages = [];

        if ($response === null) {
            return 'The payment gateway response was null.';
        }

        // General messages from the top level of the response
        if ($response->getMessages() !== null) {
            foreach ($response->getMessages()->getMessage() as $message) {
                $errorMessages[] = "Error Code: " . $message->getCode() . "\nMessage: " . $message->getText();
            }
        }

        // Transaction-specific messages (e.g., from CreateTransactionResponse)
        if (method_exists($response, 'getTransactionResponse') &&
            $response->getTransactionResponse() !== null &&
            $response->getTransactionResponse()->getMessages() !== null) {
            foreach ($response->getTransactionResponse()->getMessages() as $message) {
                // Transaction messages might have getErrorCode and getDescription
                $errorCode = property_exists($message, 'errorCode') ? $message->getErrorCode() : (property_exists($message, 'code') ? $message->getCode() : 'N/A');
                $errorText = property_exists($message, 'description') ? $message->getDescription() : (property_exists($message, 'text') ? $message->getText() : 'No description available.');
                $fullErrorText = "Transaction Error Code: " . $errorCode . "\nMessage: " . $errorText;
                
                // Avoid adding essentially duplicate messages if already captured
                if (!in_array($fullErrorText, $errorMessages)) {
                    $errorMessages[] = $fullErrorText;
                }
            }
        }

        return !empty($errorMessages) ? implode("\n---\n", $errorMessages) : 'An unspecified error occurred with the payment gateway.';
    }
}