<?php
/**
 * Subscription manager class
 * Handles subscription operations including lifetime auto-cancel
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class STRP_SUB_Subscription_Manager {

    /**
     * Single instance
     */
    private static $instance = null;

    /**
     * Get instance
     */
    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor
     */
    private function __construct() {
        // Hook into order completion to handle lifetime auto-cancel
        add_action( 'woocommerce_order_status_completed', array( $this, 'handle_lifetime_purchase' ), 10, 1 );
        add_action( 'woocommerce_order_status_processing', array( $this, 'handle_lifetime_purchase' ), 10, 1 );
    }

    private function __clone() {}

    public function __wakeup() {
        throw new \RuntimeException( 'Cannot unserialize singleton.' );
    }

    /**
     * Handle lifetime purchases and cancel active subscriptions when required.
     */
    public function handle_lifetime_purchase( $order_id ) {
        $order = wc_get_order( $order_id );

        if ( ! $order ) {
            return;
        }

        // Prevent this from running more than once for the same order.
        if ( $order->get_meta( '_strp_sub_order_processed' ) ) {
            STRP_SUB_Logger::log( 'Order already processed, skipping' );
            return;
        }

        try {
            $this->do_lifetime_purchase( $order );
        } catch ( Exception $e ) {
            // An unexpected exception (e.g., Stripe API outage, network timeout) must
            // never crash the woocommerce_order_status_completed hook. Log it, add an
            // admin-visible order note, and attempt to grant lifetime access anyway
            // so the customer is not penalised for a server-side failure.
            STRP_SUB_Logger::error(
                'Unhandled exception in handle_lifetime_purchase() for order #' . $order_id
                . ': ' . $e->getMessage()
                . ' [' . $e->getFile() . ':' . $e->getLine() . ']'
            );

            try {
                $user_id = $order->get_customer_id();
                if ( $user_id > 0 ) {
                    update_user_meta( $user_id, '_strp_sub_has_lifetime_access', 'yes' );
                    update_user_meta( $user_id, '_strp_sub_subscription_status', 'lifetime' );
                }
                $order->add_order_note(
                    __( 'SYSTEM ERROR: An exception occurred during lifetime upgrade processing. Lifetime access was force-granted. Manual subscription cancellation may be required. See error log for details.', 'subscription-manager-for-stripe' )
                );
                $order->update_meta_data( '_strp_sub_order_processed', 'yes' );
                $order->save();
            } catch ( Exception $inner ) {
                STRP_SUB_Logger::error( 'Secondary exception while handling fallback for order #' . $order_id . ': ' . $inner->getMessage() );
            }
        }
    }

    /**
     * Core lifetime purchase logic — called by handle_lifetime_purchase().
     * Separated so the outer method can wrap it cleanly in a try/catch.
     *
     * @param WC_Order $order The completed WooCommerce order.
     */
    private function do_lifetime_purchase( $order ) {
        $order_id = $order->get_id();

        // Check if order contains a lifetime product
        $has_lifetime        = false;
        $lifetime_product_id = 0;

        foreach ( $order->get_items() as $item ) {
            $product_id = $item->get_product_id();
            if ( STRP_SUB_Product_Handler::is_subscription_product( $product_id ) &&
                 STRP_SUB_Product_Handler::is_lifetime_product( $product_id ) ) {
                $has_lifetime        = true;
                $lifetime_product_id = $product_id;
                break;
            }
        }

        if ( ! $has_lifetime ) {
            return;
        }

        STRP_SUB_Logger::log( 'Lifetime product purchased in order #' . $order_id . ', checking for active subscriptions' );

        $user_id = $order->get_customer_id();

        if ( $user_id <= 0 ) {
            STRP_SUB_Logger::log( 'No user ID found for order #' . $order_id );
            return;
        }

        $stripe_customer_id = get_user_meta( $user_id, '_strp_sub_stripe_customer_id', true );

        if ( empty( $stripe_customer_id ) ) {
            STRP_SUB_Logger::log( 'No Stripe customer ID found for user #' . $user_id );
            update_user_meta( $user_id, '_strp_sub_has_lifetime_access', 'yes' );
            update_user_meta( $user_id, '_strp_sub_lifetime_purchase_date', time() );
            update_user_meta( $user_id, '_strp_sub_subscription_status', 'lifetime' );
            delete_user_meta( $user_id, '_strp_sub_active_subscription_id' );
            $order->add_order_note( __( 'Lifetime access granted. No Stripe customer ID found.', 'subscription-manager-for-stripe' ) );
            $order->update_meta_data( '_strp_sub_order_processed', 'yes' );
            $order->save();
            return;
        }

        $stripe_api    = STRP_SUB_Stripe_API::get_instance();
        $subscriptions = $stripe_api->list_customer_subscriptions( $stripe_customer_id, 'active' );

        $cancelled_count         = 0;
        $failed_count            = 0;
        $failed_subscription_ids = array();

        // 🔧 FIX: To prevent "Nuke All" cancellation algorithms, we must identify if the
        // lifetime product belongs to the identical underlying subscription tier.
        // If they possess multiple active stripe subscriptions across different WP products, 
        // we isolate specifically the one being "upgraded".
        
        // Fetch the conceptual "tier" this lifetime purchase relates to
        $lifetime_weight  = STRP_SUB_Product_Handler::get_tier_weight( $lifetime_product_id );

        foreach ( $subscriptions as $subscription ) {
            if ( in_array( $subscription->status, array( 'active', 'trialing' ), true ) ) {
                
                // Inspect the active Stripe subscription's metadata to find its origin WP product ID
                $origin_product_id = isset( $subscription->metadata['wc_product_id'] )
                    ? (int) $subscription->metadata['wc_product_id']
                    : 0;
                
                $should_cancel = false;
                
                // If it explicitly maps to our WP product, we cancel it.
                if ( $origin_product_id > 0 ) {
                    $origin_product = wc_get_product( $origin_product_id );
                    $origin_weight  = STRP_SUB_Product_Handler::get_tier_weight( $origin_product_id );
                    
                    // Cancel if they belong to the exact same product family tree
                    // (e.g. Starter Monthly -> Starter Lifetime) OR an upgrade path
                    if ( $lifetime_weight >= $origin_weight ) {
                        $should_cancel = true;
                    }
                } else {
                    // Fallback: If no origin metadata exists on the subscription, we must assume 
                    // this is extremely old plugin data and perform legacy blind cancellation
                    // to ensure they don't get double billed.
                    $should_cancel = true;
                }
                
                if ( ! $should_cancel ) {
                    continue; // Skip - this subscription belongs to a different product entirely
                }

                $result = $stripe_api->cancel_subscription( $subscription->id, false );

                if ( $result ) {
                    $cancelled_count++;
                    STRP_SUB_Logger::log( 'Successfully cancelled subscription: ' . $subscription->id );
                    $order->add_order_note( sprintf(
                        /* translators: %s: Stripe subscription ID */
                        __( 'Cancelled existing subscription %s due to lifetime purchase.', 'subscription-manager-for-stripe' ),
                        $subscription->id
                    ) );
                } else {
                    $failed_count++;
                    $failed_subscription_ids[] = $subscription->id;
                    STRP_SUB_Logger::error( 'Failed to cancel subscription: ' . $subscription->id );
                    $order->add_order_note( sprintf(
                        /* translators: %s: Stripe subscription ID */
                        __( 'WARNING: Failed to cancel subscription %s. Manual cancellation required.', 'subscription-manager-for-stripe' ),
                        $subscription->id
                    ) );
                }
            }
        }

        // Always grant lifetime access, even if automatic cancellations fail.
        update_user_meta( $user_id, '_strp_sub_has_lifetime_access', 'yes' );
        update_user_meta( $user_id, '_strp_sub_lifetime_purchase_date', time() );
        update_user_meta( $user_id, '_strp_sub_subscription_status', 'lifetime' );
        delete_user_meta( $user_id, '_strp_sub_active_subscription_id' );

        $order->update_meta_data( '_strp_sub_order_processed', 'yes' );
        $order->save();

        $this->send_lifetime_upgrade_email( $order, $cancelled_count );

        if ( $cancelled_count > 0 ) {
            STRP_SUB_Logger::log( sprintf(
                'Successfully cancelled %d subscription(s) for user #%d due to lifetime purchase',
                $cancelled_count,
                $user_id
            ) );
        }

        if ( $failed_count > 0 ) {
            STRP_SUB_Logger::error( sprintf(
                'Failed to cancel %d subscription(s) for user #%d. IDs: %s. Manual intervention required.',
                $failed_count,
                $user_id,
                implode( ', ', $failed_subscription_ids )
            ) );
            $this->notify_admin_failed_cancellation( $order, $failed_subscription_ids );
        }
    }

    /**
     * Send the lifetime access confirmation email to the customer.
     *
     * Uses the STRP_SUB_WC_Email_Lifetime WooCommerce email class so the message
     * inherits the store's branded header/footer and can be customised from
     * WooCommerce > Settings > Emails. Falls back to wp_mail() if the WC
     * mailer is not available (e.g., during unit tests).
     *
     * @param WC_Order $order           The completed order.
     * @param int      $cancelled_count Number of subscriptions cancelled during upgrade.
     */
    private function send_lifetime_upgrade_email( $order, $cancelled_count ) {
        $order_id = $order->get_id();

        // Use the registered WC_Email class when WooCommerce is loaded.
        if ( function_exists( 'WC' ) && WC()->mailer() ) {
            $mailer = WC()->mailer();
            $emails = $mailer->get_emails();

            if ( isset( $emails['STRP_SUB_WC_Email_Lifetime'] ) ) {
                $emails['STRP_SUB_WC_Email_Lifetime']->trigger( $order_id, $cancelled_count );
                STRP_SUB_Logger::log( 'Sent WC lifetime upgrade email for order #' . $order_id );
                return;
            }
        }

        // Fallback: basic wp_mail() when the WC mailer is unavailable.
        $user_id = $order->get_customer_id();
        $user    = $user_id > 0 ? get_userdata( $user_id ) : null;

        if ( ! $user ) {
            return;
        }

        $site_name = get_bloginfo( 'name' );
        $user_name = $user->display_name ?: $user->first_name;

        /* translators: %s: site name */
        $subject = sprintf( __( '[%s] Your Lifetime Access Is Now Active', 'subscription-manager-for-stripe' ), $site_name );

        $body  = '<p>' . sprintf( __( 'Hello %s,', 'subscription-manager-for-stripe' ), esc_html( $user_name ) ) . '</p>';
        $body .= '<p>' . sprintf( __( 'Congratulations! Your lifetime access to %s has been activated.', 'subscription-manager-for-stripe' ), esc_html( $site_name ) ) . '</p>';
        if ( $cancelled_count > 0 ) {
            $body .= '<p>' . __( 'Your previous recurring subscription has been cancelled automatically — no further payments will be charged.', 'subscription-manager-for-stripe' ) . '</p>';
        }
        $body .= '<p>' . __( 'You now have permanent access. Thank you for your purchase!', 'subscription-manager-for-stripe' ) . '</p>';
        $body .= '<p><em>' . sprintf( __( 'Order #%d', 'subscription-manager-for-stripe' ), (int) $order_id ) . '</em></p>';

        wp_mail( $user->user_email, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );
        STRP_SUB_Logger::log( 'Sent fallback wp_mail lifetime upgrade email to ' . $user->user_email );
    }

    /**
     * Notify admin when subscription cancellation fails
     */
    private function notify_admin_failed_cancellation( $order, $failed_subscription_ids ) {
        $admin_email = get_option( 'admin_email' );

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

        $user_id = $order->get_customer_id();
        $user    = get_userdata( $user_id );

        if ( ! $user ) {
            return;
        }

        /* translators: %s: site name */
        $subject = sprintf(
            __( '[%s] Manual Intervention Required: Failed Subscription Cancellation', 'subscription-manager-for-stripe' ),
            get_bloginfo( 'name' )
        );

        $message = sprintf(
            /* translators: 1: order ID, 2: user display name, 3: user email, 4: user ID, 5: order ID again, 6: newline-separated subscription IDs, 7: order edit URL */
            __( "Order #%1\$d: Lifetime Purchase - Subscription Cancellation Failed\n\nUser: %2\$s (%3\$s)\nUser ID: %4\$d\nOrder ID: %5\$d\n\nThe following subscriptions could not be automatically cancelled:\n%6\$s\n\nAction Required: Please manually cancel these subscriptions in the Stripe Dashboard.\n\nNote: Lifetime access has been granted to the user as they have already paid.\n\nView Order: %7\$s\nStripe Dashboard: https://dashboard.stripe.com/subscriptions\n", 'subscription-manager-for-stripe' ),
            $order->get_id(),
            $user->display_name,
            $user->user_email,
            $user_id,
            $order->get_id(),
            '- ' . implode( "\n- ", $failed_subscription_ids ),
            $order->get_edit_order_url()
        );

        wp_mail( $admin_email, $subject, $message );

        STRP_SUB_Logger::log( 'Sent admin notification for failed subscription cancellations: ' . implode( ', ', $failed_subscription_ids ) );
    }

    /**
     * Check if user has lifetime access
     */
    public static function has_lifetime_access( $user_id ) {
        return get_user_meta( $user_id, '_strp_sub_has_lifetime_access', true ) === 'yes';
    }

    /**
     * Check if user has active subscription
     */
    public static function has_active_subscription( $user_id ) {
        $subscription_id     = get_user_meta( $user_id, '_strp_sub_active_subscription_id', true );
        $subscription_status = get_user_meta( $user_id, '_strp_sub_subscription_status', true );

        return ! empty( $subscription_id ) && in_array( $subscription_status, array( 'active', 'trialing' ), true );
    }

    /**
     * Get user's subscription details
     */
    public static function get_user_subscription_details( $user_id ) {
        $subscription_id = get_user_meta( $user_id, '_strp_sub_active_subscription_id', true );

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

        $stripe_api   = STRP_SUB_Stripe_API::get_instance();
        $subscription = $stripe_api->get_subscription( $subscription_id );

        return $subscription;
    }
}
