<?php namespace App\Libraries;

use App\Models\SubscribesModel;
use CodeIgniter\HTTP\CURLRequest;
use Config\Services;
use Exception;

class PaystackClient
{
    private string $returnUrl;
    private string $cancelUrl;
    private string $secretKey;
    private string $baseApiUrl = 'https://api.paystack.co';
    private string $webhookSecretKey;

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

        $this->secretKey = $apiKeys["api_value_2"];
        $this->webhookSecretKey = $apiKeys["api_value_3"] ?? '';

        $settings = new Settings();
        $this->currencyCode = $settings->get_config("currency_code");
        $frontUrl = rtrim($settings->get_config("site_url"), '/');
        $this->returnUrl = "$frontUrl/private/profile/subscribe";
        $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 {
            // Check if this is a subscription or one-time payment
            if (isset($data["type"]) && $data["type"] === "subscription") {
                return $this->create_subscription_order($data);
            }
            
            return $this->create_onetime_order($data);
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to create order',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Cancel a subscription
     * @param string $subscriptionId The Paystack subscription ID to cancel
     * @return bool True if successful, throws exception otherwise
     */
    public function cancel_subscription(string $subscriptionId, string $token): bool
    {
        try {
            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->secretKey,
                    'Content-Type' => 'application/json',
                    'Cache-Control' => 'no-cache'
                ],
            ])->setJSON([
                'code' => $subscriptionId,
                'token' => $token
            ])->post('/subscription/disable');

            $result = json_decode($response->getBody(), true);
            
            if ($result && isset($result['status']) && $result['status']) {
                return true;
            }
            
            throw new Exception('Failed to cancel subscription: ' . $response->getBody());
        } catch (Exception $e) {
            throw new Exception('Failed to cancel subscription: ' . $e->getMessage());
        }
    }

    /**
     * Process webhook event
     * @param string $requestBody Raw request body
     * @param string $signature Paystack signature from header
     * @return array Processed webhook event data or error
     */
    public function process_webhook(string $requestBody, string $signature): array
    {
        // Verify webhook signature if provided
        if (!empty($this->webhookSecretKey) && !empty($signature)) {
            $hash = hash_hmac('sha512', $requestBody, $this->webhookSecretKey);
            if ($hash !== $signature) {
                return [
                    'event' => false,
                    'message' => 'Invalid webhook signature',
                ];
            }
        }

        try {
            // Parse the webhook payload
            $payload = json_decode($requestBody, true);
            
            if (!$payload || !isset($payload['data'])) {
                return [
                    'event' => false,
                    'message' => 'Invalid payload',
                ];
            }
            
            $data = $payload['data'];
            $eventType = $payload['event'];
            
            // Process different event types
            if ($eventType === 'charge.success') {
                $plan_id = $data['plan'] ? $data['plan']['id'] : null;

                if (!$plan_id) {
                    return [
                        'event' => true,
                        'event_type' => $eventType,
                        'resource_id' => $data['reference'],
                        'order_id' => $data['reference'],
                        'custom_id' => $data['metadata']['custom_id'],
                        'amount' => (float)($data['amount'] / 100),
                        'currency' => $data['currency'],
                        'customer_id' => $data['customer']['id'],
                        'event_data' => $data,
                    ];
                }
                
                $subscription = $this->get_target_subscriptions([
                    "customer_id" => $data['customer']['id'],
                    "plan_id" => $plan_id,
                ]);

                return [
                    'event' => true,
                    'event_type' => $eventType,
                    'resource_id' => $data['reference'],
                    'order_id' => $subscription['event'] ? $subscription['order_id'] : $data['reference'],
                    'custom_id' => $data['metadata']['custom_id'],
                    'amount' => (float)($data['amount'] / 100),
                    'currency' => $data['currency'],
                    'customer_id' => $data['customer']['id'],
                    'event_data' => $subscription['event'] ? $subscription : $data,
                ];
            }

            if ($eventType === 'subscription.not_renew') {
                $subscriptions = new SubscribesModel();
                $subscription = $subscriptions->where([
                    "subscribe_external_id" => $data['subscription_code'],
                ])->first();

                if (!$subscription) {
                    return [
                        'event' => false,
                        'message' => 'Subscription not found',
                    ];
                }

                return [
                    'event' => true,
                    'event_type' => $eventType,
                    'resource_id' => $data['subscription_code'],
                    'order_id' => $data['subscription_code'],
                    'amount' => (float)($data['amount'] / 100),
                    'custom_id' => $subscription["app_id"] . "_" . $subscription["user_id"] . "_" . $subscription["plan_id"],
                    'customer_id' => $data['customer']['id'],
                    'event_data' => $data,
                ];
            }
            
            // Default return for unhandled events
            return [
                'event' => false,
            ];
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Invalid payload',
                'error' => $e->getMessage(),
            ];
        }
    }

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

    /**
     * Create a subscription order
     * @param array $data Data for the subscription
     * @return array Response with subscription details or error
     */
    private function create_subscription_order(array $data): array
    {   
        $payload = [
            'amount' => $data['price'] * 100,
            'plan' => $data["plan_api_id"],
            'email' => $data['email'],
            'callback_url' => $this->returnUrl,
            'metadata' => [
                'custom_id' => $data["app_id"] . "_" . $data["user_id"] . "_" . $data["plan_id"],
            ]
        ];
        
        try {
            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->secretKey,
                    'Content-Type' => 'application/json',
                    'Cache-Control' => 'no-cache'
                ],
            ])->setJSON($payload)->post('/transaction/initialize');

            $result = json_decode($response->getBody(), true);
            
            if ($result && isset($result['status']) && $result['status']) {
                return [
                    'event' => true,
                    'order_id' => $result['data']['reference'],
                    'status' => 'created',
                    'approval_url' => $result['data']['authorization_url'],
                    'data' => $result['data'],
                ];
            }
            
            throw new Exception('Failed to create subscription order: ' . $response->getBody());
        } catch (Exception $e) {
            throw new Exception('Failed to create subscription order: ' . $e->getMessage());
        }
    }
    
    /**
     * Create a one-time payment order
     * @param array $data Data for the one-time payment
     * @return array Response with order details or error
     */
    private function create_onetime_order(array $data): array
    {
        $payload = [
            'amount' => $data['price'] * 100,
            'email' => $data['email'],
            'callback_url' => $this->returnUrl,
            'metadata' => [
                'custom_id' => $data["app_id"] . "_" . $data["user_id"] . "_" . $data["plan_id"],
            ]
        ];
        
        try {
            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->secretKey,
                    'Content-Type' => 'application/json',
                    'Cache-Control' => 'no-cache'
                ],
            ])->setJSON($payload)->post('/transaction/initialize');

            $result = json_decode($response->getBody(), true);
            
            if ($result && isset($result['status']) && $result['status']) {
                return [
                    'event' => true,
                    'order_id' => $result['data']['reference'],
                    'status' => 'created',
                    'approval_url' => $result['data']['authorization_url'],
                    'data' => $result['data'],
                ];
            }
            
            throw new Exception('Failed to create payment order: ' . $response->getBody());
        } catch (Exception $e) {
            throw new Exception('Failed to create payment order: ' . $e->getMessage());
        }
    }

    private function get_target_subscriptions(array $data): array
    {
        sleep(5); // Wait for 5 seconds, Paystack needs some time to process the subscription

        try {
            $response = Services::curlrequest([
                "baseURI" => $this->baseApiUrl,
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->secretKey,
                    'Content-Type' => 'application/json',
                    'Cache-Control' => 'no-cache'
                ],
            ])->get('/subscription?customer=' . $data['customer_id'] . '&plan=' . $data['plan_id']);

            $result = json_decode($response->getBody(), true);
            
            if ($result && isset($result['status']) && $result['status'] && count($result['data']) > 0) {
                
                return [
                    'event' => true,
                    'order_id' => $result['data'][0]['subscription_code'],
                    'email_token' => $result['data'][0]['email_token'],
                ];
            }
            
            throw new Exception('Failed to get subscriptions: ' . $response->getBody());
        } catch (Exception $e) {
            throw new Exception('Failed to get subscriptions: ' . $e->getMessage());
        }
    }
}
