<?php namespace App\Libraries;

use Config\Services;
use Exception;

class PaypalClient
{
    private string $baseApiUrl;
    private string $clientId;
    private string $clientSecret;
    private string $webhookId;
    private string $currencyCode;
    private string $returnUrl;
    private string $cancelUrl;

    /**
     * Create models, config and library's
     */
    function __construct()
    {
        $depositMethods = new DepositMethods();
        $apiKeys = $depositMethods->get_deposit_method_apikeys("paypal");

        $this->clientId = $apiKeys["api_value_1"];
        $this->clientSecret = $apiKeys["api_value_2"];
        $this->webhookId = $apiKeys["api_value_3"];
        
        // Set the base API URL based on environment
        $this->baseApiUrl = env('payment.sandbox') ?? false
            ? 'https://api-m.sandbox.paypal.com'
            : 'https://api-m.paypal.com';

        $settings = new Settings();
        $this->currencyCode = $settings->get_config("currency_code");
        $this->returnUrl = base_url('public/ipn/paypal/capture');

        $frontUrl = rtrim($settings->get_config("site_url"), '/');
        $this->cancelUrl = "$frontUrl/private/profile/subscribe";
    }

    /**************************************************************************************
     * PUBLIC FUNCTIONS
     **************************************************************************************/

    /**
     * Create a payment order
     * @param array $data Data for the order
     * @return array Response with order details or error
     */
    public function create_order(array $data): array
    {
        try {
            if ($data["type"] === "subscription") {
                return $this->create_subscription($data);
            }

            $token = $this->get_access_token();
            if (!$token['event']) {
                return $token;
            }

            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/json",
                    "Authorization" => "Bearer " . $token['access_token'],
                ],
            ])->setJSON([
                "intent" => "CAPTURE",
                "purchase_units" => [
                    [
                        "amount" => [
                            "currency_code" => $data["currency_code"],
                            "value" => number_format($data["price"], 2, '.', ''),
                        ],
                        "custom_id" => $data["app_id"] . "_" . $data["user_id"] . "_" . $data["plan_id"],
                    ],
                ],
                "application_context" => [
                    "return_url" => $this->returnUrl,
                    "cancel_url" => $this->cancelUrl,
                ],
            ])->post("/v2/checkout/orders");

            $data = json_decode($response->getBody(), true);

            if (isset($data['id'])) {
                // Extract the approval URL
                $approvalUrl = '';
                foreach ($data['links'] as $link) {
                    if ($link['rel'] === 'approve') {
                        $approvalUrl = $link['href'];
                        break;
                    }
                }

                return [
                    'event' => true,
                    'order_id' => $data['id'],
                    'status' => $data['status'],
                    'approval_url' => $approvalUrl,
                    'data' => $data,
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to create order',
                    'error' => $data['message'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to create order',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Capture an approved order payment
     * @param string $order_id Order ID to capture
     * @return array Response with capture details or error
     */
    public function capture_order(string $order_id): array
    {
        try {
            $token = $this->get_access_token();
            if (!$token['event']) {
                return $token;
            }

            // Send capture request this way because this library set content-length header without body
            $url = $this->baseApiUrl . "/v2/checkout/orders/{$order_id}/capture";
            $client = service('curlrequest');
            $response = $client->post($url, [
                'headers' => [
                    'Authorization' => "Bearer " . $token['access_token'],
                    'Content-Type' => 'application/json',
                ]
            ]);

            $data = json_decode($response->getBody(), true);

            if (isset($data['id']) && $data['status'] === 'COMPLETED') {
                // Extract payment details
                $captureId = $data['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
                $amount = $data['purchase_units'][0]['payments']['captures'][0]['amount']['value'] ?? null;
                $currency = $data['purchase_units'][0]['payments']['captures'][0]['amount']['currency_code'] ?? null;

                return [
                    'event' => true,
                    'order_id' => $data['id'],
                    'capture_id' => $captureId,
                    'status' => $data['status'],
                    'amount' => $amount,
                    'currency' => $currency,
                    'data' => $data,
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to capture order',
                    'error' => $data['message'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to capture order',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Get order details
     * @param string $orderId Order ID to get details for
     * @return array Response with order details or error
     */
    public function get_order(string $orderId): array
    {
        try {
            $token = $this->get_access_token();
            if (!$token['event']) {
                return $token;
            }

            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/json",
                    "Authorization" => "Bearer " . $token['access_token'],
                ],
            ])->get("/v2/checkout/orders/{$orderId}");

            $data = json_decode($response->getBody(), true);

            if (isset($data['id'])) {
                return [
                    'event' => true,
                    'order_id' => $data['id'],
                    'status' => $data['status'],
                    'data' => $data,
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to get order details',
                    'error' => $data['message'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to get order details',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Process webhook event
     * @param string $requestBody Raw request body
     * @return array Processed webhook event data or error
     */
    public function process_webhook(string $requestBody): array
    {
        try {
            // Verify webhook signature
            if (!$this->verify_webhook_signature($requestBody)) {
                return [
                    'event' => false,
                    'message' => 'Invalid webhook signature',
                ];
            }

            $eventData = json_decode($requestBody, true);
            $eventType = $eventData['event_type'] ?? '';

            // Process different event types
            switch ($eventType) {
                case 'PAYMENT.CAPTURE.COMPLETED':
                    // Payment was captured successfully
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $eventData['resource']['id'] ?? '',
                        'order_id' => $eventData['resource']['supplementary_data']['related_ids']['order_id'] ?? '',
                        'amount' => $eventData['resource']['amount']['value'] ?? '',
                        'currency' => $eventData['resource']['amount']['currency_code'] ?? '',
                        'custom_id' => $eventData['resource']['custom_id'] ?? '',
                        'customer_id' => $eventData['resource']['id'] ?? '',
                        'event_data' => $eventData,
                    ];

                case 'PAYMENT.CAPTURE.DENIED':
                    // Payment capture was denied
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $eventData['resource']['id'] ?? '',
                        'order_id' => $eventData['resource']['supplementary_data']['related_ids']['order_id'] ?? '',
                        'amount' => $eventData['resource']['amount']['value'] ?? '',
                        'currency' => $eventData['resource']['amount']['currency_code'] ?? '',
                        'custom_id' => $eventData['resource']['custom_id'] ?? '',
                        'customer_id' => $eventData['resource']['id'] ?? '',
                        'event_data' => $eventData, 
                    ];

                case 'BILLING.SUBSCRIPTION.ACTIVATED':
                    // Subscription was activated
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $eventData['resource']['id'] ?? '',
                        'order_id' => $eventData['resource']['id'] ?? '',
                        'amount' => $eventData['resource']['billing_info']['last_payment']['amount']['value'] ?? '',
                        'currency' => $eventData['resource']['billing_info']['last_payment']['amount']['currency_code'] ?? '',
                        'custom_id' => $eventData['resource']['custom_id'] ?? '',
                        'customer_id' => $eventData['resource']['subscriber']['payer_id'] ?? '',
                        'event_data' => $eventData,
                    ];

                case 'BILLING.SUBSCRIPTION.CANCELLED':
                    // Subscription was cancelled
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $eventData['resource']['id'] ?? '',
                        'order_id' => $eventData['resource']['id'] ?? '',
                        'amount' => $eventData['resource']['billing_info']['last_payment']['amount']['value'] ?? '',
                        'currency' => $eventData['resource']['billing_info']['last_payment']['amount']['currency_code'] ?? '',
                        'custom_id' => $eventData['resource']['custom_id'] ?? '',
                        'customer_id' => $eventData['resource']['subscriber']['payer_id'] ?? '',
                        'event_data' => $eventData,
                    ];

                case 'PAYMENT.SALE.COMPLETED':
                    // Payment was completed
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $eventData['resource']['id'] ?? '',
                        'order_id' => $eventData['resource']['billing_agreement_id'] ?? '',
                        'amount' => $eventData['resource']['amount']['total'] ?? '',
                        'currency' => $eventData['resource']['amount']['currency'] ?? '',
                        'custom_id' => $eventData['resource']['custom'] ?? '',
                        'event_data' => $eventData,
                    ];

                default:
                    // Other event types
                    return [
                        'event' => false,
                    ];
            }
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to process webhook',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Create a subscription
     * @param string $plan_api_id PayPal Plan ID
     * @return array Response with subscription details or error
     */
    public function create_subscription(array $data): array
    {
        try {
            $token = $this->get_access_token();
            if (!$token['event']) {
                return $token;
            }

            $payload = [
                "plan_id" => $data["plan_api_id"],
                "custom_id" => $data["app_id"] . "_" . $data["user_id"] . "_" . $data["plan_id"],
                "application_context" => [
                    "return_url" => $this->cancelUrl, // no need capture order
                    "cancel_url" => $this->cancelUrl,
                    "locale" => "en-US",
                    "shipping_preference" => "NO_SHIPPING",
                    "user_action" => "SUBSCRIBE_NOW",
                    "payment_method" => [
                        "payer_selected" => "PAYPAL",
                        "payee_preferred" => "IMMEDIATE_PAYMENT_REQUIRED"
                    ]
                ]
            ];

            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/json",
                    "Authorization" => "Bearer " . $token['access_token'],
                    "Prefer" => "return=representation"
                ],
            ])->setJSON($payload)->post("/v1/billing/subscriptions");

            $data = json_decode($response->getBody(), true);

            if (isset($data['id'])) {
                // Extract the approval URL
                $approvalUrl = '';
                foreach ($data['links'] as $link) {
                    if ($link['rel'] === 'approve') {
                        $approvalUrl = $link['href'];
                        break;
                    }
                }

                return [
                    'event' => true,
                    'subscription_id' => $data['id'],
                    'status' => $data['status'],
                    'approval_url' => $approvalUrl,
                    'data' => $data,
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to create subscription',
                    'error' => $data['message'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            log_message("error", $e);
            return [
                'event' => false,
                'message' => 'Failed to create subscription',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Cancel a subscription
     * @param string $subscriptionId Subscription ID to cancel
     * @return array Response with subscription details or error
     */
    public function cancel_subscription(string $subscriptionId): array
    {
        try {
            $token = $this->get_access_token();
            if (!$token['event']) {
                return $token;
            }

            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/json",
                    "Authorization" => "Bearer " . $token['access_token'],
                ],
            ])->setJSON(["reason" => "Not satisfied with the service"])->post("/v1/billing/subscriptions/{$subscriptionId}/cancel");

            $data = json_decode($response->getBody(), true);

            if (isset($data['id'])) {
                return [
                    'event' => true,
                    'subscription_id' => $data['id'],
                    'status' => $data['status'],
                    'data' => $data,
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to cancel subscription',
                    'error' => $data['message'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            log_message("error", $e);
            return [
                'event' => false,
                'message' => 'Failed to cancel subscription',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**************************************************************************************
     * PRIVATE FUNCTIONS
     **************************************************************************************/

    /**
     * Get basic token for authentication
     * @return string Basic token
     */
    private function get_basic_token(): string
    {
        $clientId = $this->clientId;
        $clientSecret = $this->clientSecret;

        return base64_encode("$clientId:$clientSecret");
    }

    /**
     * Get access token from PayPal API
     * @return array Response with access token or error
     */
    private function get_access_token(): array
    {
        try {
            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/x-www-form-urlencoded",
                    "Authorization" => "Basic " . $this->get_basic_token(),
                ],
            ])->setBody("grant_type=client_credentials")->post("/v1/oauth2/token");

            $data = json_decode($response->getBody(), true);

            if (isset($data['access_token'])) {
                return [
                    'event' => true,
                    'access_token' => $data['access_token'],
                    'expires_in' => $data['expires_in'],
                ];
            } else {
                return [
                    'event' => false,
                    'message' => 'Failed to get access token',
                    'error' => $data['error_description'] ?? 'Unknown error',
                ];
            }
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to get access token',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Verify webhook signature
     * @param string $requestBody Raw request body
     * @return bool True if signature is valid
     */
    private function verify_webhook_signature(string $requestBody): bool
    {
        try {
            $token = $this->get_access_token();
            if (!$token['event']) {
                return false;
            }

            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                "headers" => [
                    "Content-Type" => "application/json",
                    "Authorization" => "Bearer " . $token['access_token'],
                ],
            ])->setJSON([
                "transmission_id" => $_SERVER['HTTP_PAYPAL_TRANSMISSION_ID'],
                "transmission_time" => $_SERVER['HTTP_PAYPAL_TRANSMISSION_TIME'],
                "cert_url" => $_SERVER['HTTP_PAYPAL_CERT_URL'],
                "auth_algo" => $_SERVER['HTTP_PAYPAL_AUTH_ALGO'],
                "transmission_sig" => $_SERVER['HTTP_PAYPAL_TRANSMISSION_SIG'],
                "webhook_id" => $this->webhookId,
                "webhook_event" => json_decode($requestBody, true), 
            ])->post("/v1/notifications/verify-webhook-signature");

            $data = json_decode($response->getBody(), true);
            return isset($data['verification_status']) && $data['verification_status'] === 'SUCCESS';
        } catch (Exception $e) {
            return false;
        }
    }
}
