<?php
/**
 * PostGate Installation Class
 *
 * This file contains the plugin installation and activation handlers
 * that set up database tables, default options, and initial configuration
 * when the plugin is first activated. Ensures the plugin has all necessary
 * infrastructure before first use.
 *
 * @package PostGate
 * @subpackage Installation
 * @since 1.0.0
 * @author QodeSphere
 * @license GPL-2.0+
 */

defined( 'ABSPATH' ) || exit;

/**
 * PostGate Installation Class
 *
 * Handles plugin activation, database table creation, and default
 * option initialization. This class implements the complete setup process
 * with the following features:
 *
 * - Database table creation using dbDelta() for safe upgrades
 * - Unique installation key generation for security
 * - Default option initialization with appropriate defaults
 * - Stripe credential storage setup (live and sandbox modes)
 * - Bridge service configuration for OAuth integration
 * - Idempotent operations (safe to run multiple times)
 *
 * Database Tables Created:
 * - postgate_orders: Stores all purchase orders and subscriptions
 * - postgate_webhooks: Logs Stripe webhook events for debugging
 *
 * Options Initialized:
 * - postgate_install_key: Unique 64-character hex installation identifier
 * - postgate_stripe_connect_mode: Default connection mode (sandbox)
 * - Stripe credentials for live and sandbox modes (empty by default)
 * - Bridge service URL and shared secret for OAuth communication
 *
 * The class uses dbDelta() for table creation, which intelligently updates
 * existing tables rather than failing, making it safe for both new installations
 * and plugin updates.
 *
 * All methods are static as they are called during activation without instantiation.
 * The activation hook is registered in the main plugin file (postgate.php).
 *
 * @since 1.0.0
 */
class PostGate_Installation {

	/**
	 * Main activation method called by WordPress.
	 *
	 * This is the primary entry point for plugin activation. It orchestrates
	 * the complete setup process in two phases:
	 * 1. Database Setup: Creates required tables for orders and webhooks
	 * 2. Options Setup: Initializes all plugin configuration options
	 * 3. Activation Redirect: Sets flag for dashboard redirect after activation
	 *
	 * This method is registered as the activation hook in the main plugin file
	 * and is called automatically by WordPress when the plugin is activated
	 * through the admin interface or via WP-CLI.
	 *
	 * The method is idempotent - it can be safely called multiple times without
	 * causing errors. Existing tables are updated (not dropped), and existing
	 * options are preserved.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function activate() {
		// Create database tables
		self::create_database_tables();

		// Initialize all options
		self::initialize_options();

		// Set activation redirect flag (expires in 30 seconds)
		// This will trigger a redirect to the dashboard on first admin page load
		// The actual redirect is handled by maybe_redirect_after_activation()
		// which is registered as an admin_init hook in PostGate_Core::init()
		set_transient( 'postgate_activation_redirect', true, 30 );
	}

	/**
	 * Redirect to dashboard after plugin activation.
	 *
	 * This method is called on every admin_init hook and checks for the
	 * activation redirect transient flag. If found, it redirects the user
	 * to the PostGate dashboard.
	 *
	 * Why this can't be in activate():
	 * - The activate() method runs during plugin activation
	 * - The redirect needs to happen on the NEXT page load
	 * - Therefore, we need a hook (admin_init) that runs on EVERY load
	 * - The transient acts as a one-time flag set during activation
	 *
	 * Safety checks ensure the redirect only occurs when appropriate:
	 * - Only redirects if the transient flag exists
	 * - Only for users with manage_options capability
	 * - Not during AJAX requests
	 * - Not during cron jobs
	 * - Not during WP-CLI operations
	 * - Not during bulk plugin activations (transient prevents this)
	 *
	 * The transient flag is set in activate() and expires after 30 seconds
	 * to prevent stale redirects on subsequent visits.
	 *
	 * Uses wp_safe_redirect() for security and exits immediately after redirect
	 * to prevent any additional output.
	 *
	 * @since 1.0.1
	 * @access public
	 *
	 * @return void
	 */
	public static function maybe_redirect_after_activation() {
		// Only redirect if transient flag exists
		if ( ! get_transient( 'postgate_activation_redirect' ) ) {
			return;
		}

		// Delete the transient immediately to prevent multiple redirects
		delete_transient( 'postgate_activation_redirect' );

		// Safety checks - only redirect in appropriate contexts
		if (
			! current_user_can( 'manage_options' ) ||
			wp_doing_ajax() ||
			wp_doing_cron() ||
			( defined( 'WP_CLI' ) && WP_CLI )
		) {
			return;
		}

		// Perform the redirect to the dashboard
		wp_safe_redirect( admin_url( 'admin.php?page=postgate-dashboard' ) );
		exit;
	}

	/**
	 * Create all required database tables.
	 *
	 * Sets up custom database tables needed by the plugin:
	 * - postgate_orders: Stores purchase and subscription data
	 * - postgate_webhooks: Logs webhook events for debugging and auditing
	 *
	 * Uses dbDelta() through execute_database_creation() which provides:
	 * - Intelligent table creation (creates if missing)
	 * - Safe schema updates (modifies existing tables when needed)
	 * - Automatic index management
	 *
	 * Tables use the WordPress table prefix to ensure compatibility with
	 * multisite installations and custom database configurations.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function create_database_tables() {
		self::create_orders_table();
		self::create_webhooks_table();
	}

	/**
	 * Initialize all plugin options with default values.
	 *
	 * Sets up required plugin options in the WordPress options table:
	 * 1. Installation key: Unique identifier for this installation
	 * 2. Mode option: Default Stripe connection mode (sandbox)
	 * 3. Stripe options: Credential storage for live and sandbox modes
	 * 4. Bridge options: OAuth service configuration
	 *
	 * All options use add_option_if_not_exists() to preserve existing
	 * values, making this method safe to call on plugin updates.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function initialize_options() {
		self::setup_install_key();
		self::setup_mode_option();
		self::setup_stripe_options();
		self::setup_bridge_options();
	}

	/**
	 * Setup unique installation key for this WordPress site.
	 *
	 * Generates and stores a cryptographically secure 64-character hexadecimal
	 * installation key if one doesn't already exist. This key serves as a unique
	 * identifier for this specific plugin installation.
	 *
	 * The key is generated using random_bytes(32) which produces 32 random bytes
	 * that are then converted to 64 hexadecimal characters.
	 *
	 * Uses:
	 * - Installation tracking and identification
	 * - Request signing for bridge server communication
	 * - Security token generation
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function setup_install_key() {
		if ( ! get_option( 'postgate_install_key' ) ) {
			$key = bin2hex( random_bytes( 32 ) ); // 64 hex chars
			self::add_option_if_not_exists( 'postgate_install_key', $key );
		}
	}

	/**
	 * Setup default Stripe connection mode option.
	 *
	 * Initializes the Stripe connection mode to 'sandbox' (test mode) if not
	 * already set. This ensures new installations start in test mode to prevent
	 * accidental live transactions during setup and testing.
	 *
	 * Users can change this to 'live' mode through the plugin settings once
	 * they've completed testing and are ready for production use.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function setup_mode_option() {
		self::add_option_if_not_exists( 'postgate_stripe_connect_mode', 'sandbox' );
	}

	/**
	 * Setup Stripe-related options for both live and sandbox modes.
	 *
	 * Initializes empty storage for Stripe credentials in both modes:
	 * - connected_acct: Stripe Connect account ID
	 * - access_token: OAuth access token for API requests
	 * - refresh_token: OAuth refresh token for token renewal
	 * - publishable_key: Public key for Stripe.js on frontend
	 *
	 * Creates options for both 'live' and 'sandbox' modes, allowing the plugin
	 * to maintain separate credentials for testing and production environments.
	 *
	 * All credentials are initially empty and must be obtained through the
	 * Stripe Connect OAuth flow via the bridge server.
	 *
	 * Note: Webhook secrets are configured separately through manual setup
	 * in the Stripe dashboard and stored via the settings interface.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function setup_stripe_options() {
		$modes = [ 'live', 'sandbox' ];
		// Note: webhook_secret removed since webhooks are now manually configured
		$stripe_keys = [ 'connected_acct', 'access_token', 'refresh_token', 'publishable_key' ];

		foreach ( $modes as $mode ) {
			foreach ( $stripe_keys as $key ) {
				$option_name = "postgate_{$key}_{$mode}";
				self::add_option_if_not_exists( $option_name, '' );
			}
		}
	}

	/**
	 * Setup bridge service options for Stripe Connect OAuth.
	 *
	 * Initializes configuration for the PostGate Bridge service which acts as
	 * an intermediary for Stripe Connect OAuth authentication:
	 *
	 * - postgate_bridge_url: Bridge server endpoint URL
	 *   Default: https://postgate-bridge.qodesphere.com
	 *
	 * - postgate_bridge_shared_secret: Shared secret for request signing
	 *   Used to authenticate requests between WordPress and the bridge server
	 *
	 * The bridge server manages OAuth credentials securely, preventing sensitive
	 * Stripe tokens from being stored directly in WordPress. This architecture
	 * provides better security and simplifies the connection process for users.
	 *
	 * These values can be customized for self-hosted bridge deployments or
	 * development environments.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function setup_bridge_options() {
		$bridge_options = [
			'postgate_bridge_url' => 'https://postgate-bridge.qodesphere.com',
			'postgate_bridge_shared_secret' => '9e16f8d5f93640ae25109b07dadc688109f481551ac5ef62e2b2322e3500d80a8d354580475c65c70c3a16cdc799810d61e49473871e0cde21340ae25bfcbd66'
		];

		foreach ( $bridge_options as $option_name => $default_value ) {
			self::add_option_if_not_exists( $option_name, $default_value );
		}
	}

	/**
	 * Helper method to add option only if it doesn't exist.
	 * 
	 * @since 1.0.0
	 * @param string $option_name The option name.
	 * @param mixed $default_value The default value.
	 * @return void
	 */
	private static function add_option_if_not_exists( string $option_name, $default_value ): void {
		if ( false === get_option( $option_name, false ) ) {
			add_option( $option_name, $default_value, '', false );
		}
	}

	/**
	 * Create the orders table for storing purchase and subscription data.
	 *
	 * Creates wp_postgate_orders table with the following schema:
	 *
	 * Columns:
	 * - id: Auto-increment primary key
	 * - created_at: Order creation timestamp (auto-populated)
	 * - updated_at: Last update timestamp (auto-updated)
	 * - user_id: WordPress user ID who made the purchase
	 * - stripe_account_id: Connected Stripe account identifier
	 * - type: Order type ('one_time' or 'subscription')
	 * - status: Order status (pending, paid, failed, refunded, review, cancelled)
	 * - created_via: Order creation method ('webhook', 'return_url', 'manual', 'webhook_failed')
	 * - currency: Three-letter currency code (e.g., 'usd')
	 * - amount_cents: Total amount in cents
	 * - fee_cents: Platform fee in cents
	 * - fee_percent: Platform fee percentage (if applicable)
	 * - recurring_interval: Billing interval for subscriptions (month, year, etc.)
	 * - stripe_payment_intent_id: Stripe PaymentIntent ID for one-time purchases
	 * - stripe_checkout_session_id: Stripe Checkout Session ID
	 * - stripe_subscription_id: Stripe Subscription ID for recurring purchases
	 * - stripe_customer_id: Stripe Customer ID
	 * - purchased_product_id: Stripe Product ID that was purchased
	 * - purchased_price_id: Stripe Price ID that was purchased (specific billing option)
	 * - product_name: Human-readable product/price name
	 * - unlocked_content: JSON array of post IDs this purchase grants access to
	 * - admin_notes: Optional notes when order created manually by administrator
	 * - created_by_admin_id: WordPress user ID of admin who created manual order (if applicable)
	 * - origin_post_id: Post ID where the purchase was initiated (for analytics)
	 *
	 * Indexes:
	 * - PRIMARY KEY on id
	 * - idx_user_id: Fast lookup by WordPress user
	 * - idx_stripe_account_id: Fast lookup by Stripe account
	 * - idx_stripe_payment_intent_id: Webhook lookups by payment intent
	 * - idx_stripe_subscription_id: Webhook lookups by subscription
	 * - idx_purchased_product_id: Analytics by product
	 * - idx_purchased_price_id: Analytics by price
	 * - idx_origin_post_id: Analytics by originating post
	 * - idx_created_via: Fast filtering by creation method (for issue #84 tracking)
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function create_orders_table() {
		global $wpdb;

		$table_name = $wpdb->prefix . 'postgate_orders';
		$charset_collate = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE {$table_name} (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			user_id BIGINT UNSIGNED NOT NULL,
			stripe_account_id VARCHAR(255) NOT NULL DEFAULT '',
			type ENUM('one_time','subscription') NOT NULL,
			status VARCHAR(32) NOT NULL DEFAULT 'pending',
			created_via VARCHAR(50) NOT NULL DEFAULT 'webhook',
			currency VARCHAR(8) NOT NULL DEFAULT 'usd',
			amount_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
			fee_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
			fee_percent DECIMAL(5,2) UNSIGNED NULL DEFAULT NULL,
			recurring_interval VARCHAR(16) NULL,
			stripe_payment_intent_id VARCHAR(255) NULL,
			stripe_checkout_session_id VARCHAR(255) NULL,
			stripe_subscription_id VARCHAR(255) NULL,
			stripe_customer_id VARCHAR(255) NULL,
			purchased_product_id VARCHAR(255) NULL,
			purchased_price_id VARCHAR(255) NULL,
			product_name VARCHAR(255) NULL,
			unlocked_content TEXT NULL,
			admin_notes TEXT NULL,
			created_by_admin_id BIGINT UNSIGNED NULL,
			origin_post_id BIGINT UNSIGNED NULL,
			PRIMARY KEY (id),
			KEY idx_user_id (user_id),
			KEY idx_stripe_account_id (stripe_account_id),
			KEY idx_stripe_payment_intent_id (stripe_payment_intent_id),
			KEY idx_stripe_subscription_id (stripe_subscription_id),
			KEY idx_purchased_product_id (purchased_product_id),
			KEY idx_purchased_price_id (purchased_price_id),
			KEY idx_origin_post_id (origin_post_id),
			KEY idx_created_via (created_via)
		) $charset_collate;";

		self::execute_database_creation( $sql );
	}

	/**
	 * Create the webhooks table for logging Stripe webhook events.
	 *
	 * Creates wp_postgate_webhooks table with the following schema:
	 *
	 * Columns:
	 * - id: Auto-increment primary key
	 * - event_id: Unique Stripe event ID (e.g., 'evt_...')
	 * - event_type: Type of webhook event (e.g., 'checkout.session.completed')
	 * - stripe_account_id: Connected Stripe account that sent the event
	 * - received_at: Timestamp when webhook was received
	 * - data: Full webhook payload as JSON (LONGTEXT for large payloads)
	 *
	 * Indexes:
	 * - PRIMARY KEY on id
	 * - UNIQUE idx_event_id: Prevents duplicate event processing
	 * - idx_event_type: Fast filtering by event type
	 * - idx_received_at: Chronological ordering and cleanup queries
	 * - idx_stripe_account_id: Fast lookup by Stripe account
	 *
	 * Purpose:
	 * - Debugging webhook processing issues
	 * - Audit trail of all Stripe events
	 * - Idempotency checking (prevents duplicate processing via unique event_id)
	 * - Historical event data for support and troubleshooting
	 *
	 * The LONGTEXT data column can store very large webhook payloads including
	 * expanded objects and nested data structures from Stripe.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private static function create_webhooks_table() {
		global $wpdb;

		$table_name = $wpdb->prefix . 'postgate_webhooks';
		$charset_collate = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE {$table_name} (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			event_id VARCHAR(255) NOT NULL,
			event_type VARCHAR(100) NOT NULL,
			stripe_account_id VARCHAR(255) DEFAULT NULL,
			received_at DATETIME NOT NULL,
			data LONGTEXT,
			PRIMARY KEY (id),
			UNIQUE KEY idx_event_id (event_id),
			KEY idx_event_type (event_type),
			KEY idx_received_at (received_at),
			KEY idx_stripe_account_id (stripe_account_id)
		) $charset_collate;";

		self::execute_database_creation( $sql );
	}

	/**
	 * Helper method to execute database table creation using dbDelta().
	 *
	 * Uses WordPress's dbDelta() function which provides intelligent table
	 * creation and updates:
	 * - Creates tables if they don't exist
	 * - Modifies existing tables to match the schema
	 * - Adds missing columns and indexes
	 * - Does NOT remove existing columns (safe for data preservation)
	 *
	 * Requires wp-admin/includes/upgrade.php to be loaded, which this method
	 * does automatically.
	 *
	 * This approach makes plugin updates safe - the same schema definition
	 * can be used to both create new tables and update existing ones without
	 * data loss.
	 *
	 * @since 1.0.0
	 *
	 * @param string $sql The SQL CREATE TABLE statement to execute.
	 *                    Must follow dbDelta() requirements (specific formatting).
	 *
	 * @return void
	 */
	private static function execute_database_creation( string $sql ): void {
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );
	}
}
