<?php namespace App\Libraries;

use App\Models\SubscribesModel;
use Exception;
use Stripe\StripeClient as StripeClientLib;
use Stripe\Webhook as StripeWebhook;
use Stripe\Event;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Exception\UnexpectedValueException;

require_once(APPPATH . 'ThirdParty/Stripe/init.php');

class StripeClient
{
    private string $returnUrl;
    private string $cancelUrl;
    private StripeClientLib $stripe;
    private string $webhookSecretKey;

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

        $this->stripe = new StripeClientLib($apiKeys["api_value_1"]);
        $this->webhookSecretKey = $apiKeys["api_value_2"];

        $settings = new Settings();
        $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 {
            $mode = $data["type"] === "subscription" ? "subscription" : "payment";

            $session = $this->stripe->checkout->sessions->create([
                "success_url" => $this->returnUrl,
                "cancel_url"  => $this->cancelUrl,
                "mode"        => $mode,
                "line_items"  => [[
                    "price"    => $data["plan_api_id"],
                    "quantity" => 1,
                ]],
                "metadata"    => [
                    "custom_id" => $data["app_id"] . "_" . $data["user_id"] . "_" . $data["plan_id"],
                ],
            ]);

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

    /**
     * Get checkout session by payment intent id
     * @param string $paymentIntentId Payment intent id
     * @return array Checkout session data or error
     */
    public function get_checkout_session_by_payment(string $paymentIntentId): array
    {
        try {
            $session = $this->stripe->checkout->sessions->all([
                "payment_intent" => $paymentIntentId,
            ]);

            if (!$session['data'][0]) {
                return [
                    'event' => false,
                    'message' => 'Checkout session not found',
                ];
            }

            return [
                'event' => true,
                'resource_id' => $session['data'][0]['id'],
                'order_id' => $session['data'][0]->mode === "subscription"
                    ? $session['data'][0]->subscription
                    : $session['data'][0]->payment_intent,
                'custom_id' => $session['data'][0]['metadata']['custom_id'],
                'customer_id' => $session['data'][0]['customer'] ?? $session['data'][0]['customer_details']['email'],
                'event_data' => $session['data'][0],
            ];
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to get checkout session',
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Get invoice by id
     * @param string $invoiceId Invoice id
     * @return array Invoice data or error
     */
    public function get_invoice_by_id(string $invoiceId): array
    {
        try {
            $invoice = $this->stripe->invoices->retrieve($invoiceId);

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

            $asubscriptions = new SubscribesModel();
            $subscription = $asubscriptions->where([
                "subscribe_external_id" => $invoice['subscription'],
            ])->first();

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

            return [
                'event' => true,
                'resource_id' => $invoice['id'],
                'order_id' => $invoice['subscription'],
                'custom_id' => $subscription["app_id"] . "_" . $subscription["user_id"] . "_" . $subscription["plan_id"],
                'customer_id' => $invoice['customer'] ?? $invoice['customer_details']['email'],
                'event_data' => $invoice,
            ];
        } catch (Exception $e) {
            return [
                'event' => false,
                'message' => 'Failed to get invoice',
                '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 and parse webhook
            $event = $this->verify_webhook_signature($requestBody);

            if (!$event) {
                return [
                    'event' => false,
                    'message' => 'Invalid webhook signature',
                ];
            }

            $metadata = $event->data["object"]->metadata;

            // Process different event types
            switch ($event->type) {
                case 'checkout.session.completed':
                    return [
                        'event' => true,
                        'event_type' => $event->type,
                        'resource_id' => $event->data["object"]->id ?? '',
                        'order_id' => $event->data["object"]->mode === "subscription"
                            ? $event->data["object"]->subscription
                            : $event->data["object"]->payment_intent,
                        'amount' => $event->data["object"]->amount_total / 100,
                        'currency' => $event->data["object"]->currency,
                        'custom_id' => $metadata->custom_id,
                        'customer_id' => $event->data["object"]->customer ?? $event->data["object"]->customer_details->email,
                        'event_data' => $event->data,
                    ];
                case 'customer.subscription.updated':
                    $asubscriptions = new SubscribesModel();
                    $subscription = $asubscriptions->where([
                        "customer_external_id" => $event->data["object"]->customer,
                        "subscribe_external_id" => $event->data["object"]->id,
                    ])->first();

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

                    return [
                        'event' => true,
                        'event_type' => $event->type,
                        'resource_id' => $event->data["object"]->id ?? '',
                        'order_id' => $event->data["object"]->id ?? '',
                        'amount' => $event->data["object"]->plan->amount / 100,
                        'currency' => $event->data["object"]->plan->currency,
                        'custom_id' => $subscription["app_id"] . "_" . $subscription["user_id"] . "_" . $subscription["plan_id"],
                        'cancel_at' => $event->data["object"]->cancel_at,
                        'canceled_at' => $event->data["object"]->canceled_at,
                        'current_period_end' => $event->data["object"]->current_period_end,
                        'customer_id' => $event->data["object"]->customer ?? $event->data["object"]->customer_details->email,
                        'event_data' => $event->data,
                    ];
                case 'payment_intent.succeeded':
                    $session = $event->data["object"]->invoice
                        ? $this->get_invoice_by_id($event->data["object"]->invoice)
                        : $this->get_checkout_session_by_payment($event->data["object"]->id);

                    if (!$session["event"]) {
                        return [
                            'event' => false,
                            'message' => 'Failed to get session data',
                            'error' => $session["error"],
                        ];
                    }

                    return [
                        'event' => true,
                        'event_type' => $event->type,
                        'resource_id' => $session["resource_id"],
                        'order_id' => $session["order_id"],
                        'amount' => $event->data["object"]->amount / 100,
                        'currency' => $event->data["object"]->currency,
                        'custom_id' => $session["custom_id"],
                        'customer_id' => $session["customer_id"],
                        'event_data' => $event->data,
                    ];

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

    /**
     * Cancel subscription
     * @param string $subscriptionId Subscription id
     * @return void
     */
    public function cancel_subscription(string $subscriptionId): void
    {
        $this->stripe->subscriptions->cancel($subscriptionId);
    }

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

    /**
     * Verify webhook signature
     * @param string $requestBody Raw request body
     * @return Event|false Event if signature is valid
     */
    private function verify_webhook_signature(string $requestBody): Event|false
    {
        if (empty($_SERVER['HTTP_STRIPE_SIGNATURE'])) {
            return false;
        }

        $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];

        try {
            return StripeWebhook::constructEvent($requestBody, $sig_header, $this->webhookSecretKey);
        } catch (UnexpectedValueException $e) {
            return false;
        } catch (SignatureVerificationException $e) {
            return false;
        }
    }
}
