<?php

// Will rely on pmpro library instead, this kept in case of future use
//require_once CHURNSOLUTION_DIR . '/vendor/autoload.php';

if (!defined('ABSPATH')) exit;

add_action('plugins_loaded', array('Churnsolution_Stripe_Gateway', 'init'));

class Churnsolution_Stripe_Gateway
{

    private static $stripe;

    public static function init()
    {

        $secret_key = null;

        if (class_exists("PMProGateway_stripe")) {
            PMProGateway_stripe::loadStripeLibrary();
        }

        if (class_exists("PMPro_Subscription")) {

            $secret_key = self::pmpro_get_secret_key();
        }

        if (!empty($secret_key)) {
            self::$stripe = new Stripe\StripeClient($secret_key);
        }
    }

    public static function pause($external_id, $number_of_months)
    {
        self::validate_stripe_initialized();

        $subscription = self::validate_subscription_exists($external_id);

        $current_period_end = $subscription->current_period_end;
        $new_period_end = $current_period_end + $number_of_months * 30 * 24 * 60 * 60;

        $subscription = self::$stripe->subscriptions->update(
            $external_id,
            ['pause_collection' => array(
                'resumes_at' => $new_period_end,
                'behavior' => 'mark_uncollectible'
            )]
        );

    }

    public static function apply_coupon($external_id, $coupon_code)
    {
        self::validate_stripe_initialized();

        $subscription = self::validate_subscription_exists($external_id);

        $subscription = self::$stripe->subscriptions->update(
            $external_id,
            ['coupon' => $coupon_code]
        );

    }

    public static function extend_free_trial($external_id, $number_of_days)
    {
        self::validate_stripe_initialized();

        $subscription = self::validate_subscription_exists($external_id);

        if ($subscription->status != Stripe\Subscription::STATUS_TRIALING) {
            throw new Exception("subscription: " . esc_attr($external_id) . " is not trialing", 400);
        }

        $current_trial_end = $subscription->trial_end; // in seconds
        $new_trial_end = $current_trial_end + $number_of_days * 24 * 60 * 60;

        $subscription = self::$stripe->subscriptions->update(
            $external_id,
            ['trial_end' => $new_trial_end]
        );

    }


    public static function cancel_subscription_via_pmpro(WP_User $user, $level_id)
    {

        if (!class_exists("PMPro_Subscription")) {
            return;
        }

        $user_id = $user->ID;
        $subscriptions = PMPro_Subscription::get_subscriptions_for_user($user_id, $level_id);

        if (empty($subscriptions)) {
            return;
        }

        foreach ($subscriptions as $subscription) {

            $next_payment_date = $subscription->get_next_payment_date();
            // Set the enddate to the next payment date.
            pmpro_set_expiration_date( $user_id, $level_id, $next_payment_date );

            // Cancel the subscriptions.
            $subscription->cancel_at_gateway();

            // Send an email to the member.
            $myemail = new PMProEmail();
            $myemail->sendCancelOnNextPaymentDateEmail( $user, $level_id );

            // Send an email to the admin.
            $myemail = new PMProEmail();
            $myemail->sendCancelOnNextPaymentDateAdminEmail( $user, $level_id );

        }

    }

    /**
     * Reflect a Stripe subscription (created during reactivation) into PMPro.
     *
     * PMPro does not listen to Stripe's customer.subscription.created webhook, so
     * a subscription created directly in Stripe never seeds a PMPro record. We
     * seed it here: restore the membership level, then create the subscription
     * row. PMPro_Subscription::create() is idempotent (bails if a row with this
     * transaction id already exists) and its ->update() pulls status, next
     * payment date, billing amount and cycle straight from Stripe.
     *
     * @param int         $user_id         WordPress user id.
     * @param string      $subscription_id Stripe subscription id (sub_...).
     * @param int|null    $level_id        PMPro level id; derived from the Stripe
     *                                     product when omitted.
     * @return array Summary of what was reflected.
     */
    public static function reactivate_pmpro_subscription($user_id, $subscription_id, $level_id = null)
    {
        self::validate_stripe_initialized();

        if (!class_exists('PMPro_Subscription')) {
            throw new Exception('PMPro subscriptions are not available', 400);
        }

        // Validates the subscription exists in Stripe (throws 404 otherwise).
        $external_subscription = self::validate_subscription_exists($subscription_id);

        // Derive the membership level from the Stripe product when not provided.
        if (empty($level_id)) {
            $product_id = $external_subscription->items->data[0]->price->product;
            $level_id = self::get_level_from_product_id($product_id);
        }

        if (empty($level_id)) {
            throw new Exception('Could not determine a membership level for subscription: ' . esc_attr($subscription_id), 422);
        }

        $level_id = (int)$level_id;
        $gateway_environment = get_option('pmpro_gateway_environment');

        // Restore the membership level first. Reactivation targets already
        // cancelled subscriptions, so the "cancel previous subscriptions"
        // side-effect of changing levels is a no-op here. Doing this before
        // creating the new record ensures the new subscription is not cancelled.
        if (!pmpro_hasMembershipLevel($level_id, $user_id)) {
            pmpro_changeMembershipLevel($level_id, $user_id);
        }

        // Seed the subscription record. Idempotent; PMPro syncs the details.
        PMPro_Subscription::create(array(
            'user_id'                     => (int)$user_id,
            'membership_level_id'         => $level_id,
            'gateway'                     => 'stripe',
            'gateway_environment'         => $gateway_environment,
            'subscription_transaction_id' => $subscription_id,
        ));

        return array(
            'user_id'     => (int)$user_id,
            'level_id'    => $level_id,
            'external_id' => $subscription_id,
            'status'      => $external_subscription->status,
        );
    }

    private static function validate_subscription_exists($external_id)
    {
        self::validate_stripe_initialized();

        try {
            return self::$stripe->subscriptions->retrieve($external_id);
        } catch (\Stripe\Exception\ApiErrorException $e) {
            $code = $e->getStripeCode();

            if ($code == \Stripe\ErrorObject::CODE_RESOURCE_MISSING) {
                throw new Exception(
                    "No subscriptions with id: " . esc_attr($external_id) . " were found",
                    404
                );
            }

            throw $e;
        }
    }

    public static function sync_pmpro_subscription($user_id)
    {

        if (!class_exists("PMPro_Subscription") or !class_exists("PMPro_Membership_Level")) {
            return;
        }

        $pmpro_subscriptions = PMPro_Subscription::get_subscriptions_for_user($user_id);

        foreach ($pmpro_subscriptions as $pmpro_subscription) {

            // Update next payment date
            $pmpro_subscription->update();

            // Plan changes updates
            $external_id = $pmpro_subscription->get_subscription_transaction_id();
            $external_subscription = self::$stripe->subscriptions->retrieve($external_id);

            if (empty($external_subscription)) {
                continue;
            }

            // Get product id
            $items = $external_subscription->items;
            $item = $items->data[0];
            $price = $item->price;
            $product_id = $price->product;

            // get level id by product id
            $external_level_id = self::get_level_from_product_id($product_id);

            // if level not found, do nothing
            if(empty($external_level_id)){
                continue;
            }

            $current_level_id = $pmpro_subscription->get_membership_level_id();

            // if same, nothing to sync
            if ($current_level_id == $external_level_id) {
                continue;
            }

            $membership_level = new PMPro_Membership_Level($external_level_id);

            global $wpdb;

            // update level in database
            $wpdb->update(
                $wpdb->prefix . 'pmpro_memberships_users',
                array(
                    'membership_id' => $external_level_id,
                    'billing_amount' => $membership_level->billing_amount,
                    'cycle_period' => $membership_level->cycle_period
                ),
                array(
                    'user_id' => $user_id,
                    'status' => 'active',
                    'membership_id' => $current_level_id
                )
            );

            $wpdb->update(
                $wpdb->prefix . 'pmpro_subscriptions',
                array(
                    'membership_level_id' => $external_level_id,
                    'billing_amount' => $membership_level->billing_amount,
                    'cycle_period' => $membership_level->cycle_period
                ),
                array(
                    'user_id' => $user_id,
                    'status' => 'active',
                    'membership_level_id' => $current_level_id
                )
            );
        }

    }

    public static function get_external_subscription($external_id)
    {
        self::validate_stripe_initialized();

        try {
            $external_subscription = self::$stripe->subscriptions->retrieve($external_id);
        } catch (\Stripe\Exception\ApiErrorException $e) {
            return null;
        }
        return $external_subscription;
    }

    public static function get_external_upcoming_invoice($external_id)
    {
        self::validate_stripe_initialized();

        try {
            $upcoming_invoice = self::$stripe->invoices->createPreview(['subscription' => $external_id]);
        } catch (\Stripe\Exception\ApiErrorException $e) {
            return null;
        }
        return $upcoming_invoice;
    }

    public static function get_level_from_product_id($product_id)
    {
        global $wpdb;

        $sql_query = $wpdb->prepare("
                    SELECT * 
                    FROM `$wpdb->pmpro_membership_levelmeta` 
                    WHERE meta_value = %s",
            esc_sql($product_id)
        );

        $membership_levelmeta_rows = $wpdb->get_results($sql_query);

        if (empty($membership_levelmeta_rows)) {
            return null;
        }

        $membership_levelmeta = $membership_levelmeta_rows[0];


        return $membership_levelmeta->pmpro_membership_level_id;
    }

    private static function pmpro_is_using_legacy_keys()
    {
        $r = !empty(get_option('pmpro_stripe_secretkey')) && !empty(get_option('pmpro_stripe_publishablekey'));
        return $r;
    }

    private static function pmpro_get_secret_key()
    {
        if (self::pmpro_is_using_legacy_keys()) {
            $secretkey = get_option('pmpro_stripe_secretkey');
        } else {
            $secretkey = get_option('pmpro_gateway_environment') === 'live'
                ? get_option('pmpro_live_stripe_connect_secretkey')
                : get_option('pmpro_sandbox_stripe_connect_secretkey');
        }
        return $secretkey;
    }

    private static function validate_stripe_initialized()
    {
        if (empty(self::$stripe)) {
            throw new Exception("Stripe is not configured properly", 400);
        }
    }
    public static function pmpro_get_secrets()
    {
        $connectSecretKey = null;
        $connectPublishableKey = null;
        $legacySecretKey = null;
        $legacyPublishableKey = null;
        $liveMode = get_option('pmpro_gateway_environment') === 'live';

        if (self::pmpro_is_using_legacy_keys()) {
            $legacySecretKey = get_option('pmpro_stripe_secretkey');
            $legacyPublishableKey = get_option('pmpro_stripe_publishablekey');
        } else {
            $connectSecretKey = get_option('pmpro_gateway_environment') === 'live'
                ? get_option('pmpro_live_stripe_connect_secretkey')
                : get_option('pmpro_sandbox_stripe_connect_secretkey');

            $connectPublishableKey = get_option('pmpro_gateway_environment') === 'live'
                ? get_option('pmpro_live_stripe_connect_publishablekey')
                : get_option('pmpro_sandbox_stripe_connect_publishablekey');
        }

        return [
            "connect_public_key" => $connectPublishableKey,
            "connect_private_key" => $connectSecretKey,
            "legacy_private_key" => $legacySecretKey,
            "legacy_public_key" => $legacyPublishableKey,
            "live_mode" => $liveMode
        ];
    }

}