<?php
/**
 * Installer — creates all plugin tables and schedules recurring events.
 *
 * @package CartCue
 */

namespace CartCue;

defined( 'ABSPATH' ) || exit;

class Installer {

	/** Action Scheduler group for CartCue cron hooks. */
	public const AS_GROUP = 'cartcue';

	/** Set on deactivate when Action Scheduler is not ready yet. */
	private const DEFERRED_UNSCHEDULE_OPTION = 'cartcue_pending_as_unschedule';

	/** Pro-only recurring hooks (must not run in Lite). */
	private const PRO_ONLY_HOOKS = [
		'cartcue_check_expiring_cards',
		'cartcue_check_card_expiry',
		'cartcue_weekly_digest',
	];

	public static function activate(): void {
		self::create_tables();
		self::run_when_action_scheduler_ready(
			static function (): void {
				self::schedule_events();
				if ( defined( 'CARTCUE_PRO_VERSION' ) ) {
					self::schedule_pro_events();
				} else {
					self::unschedule_pro_only_hooks();
				}
			}
		);
		// Flush rewrite rules for EmailTracker endpoints.
		flush_rewrite_rules();
		Plugin::enforce_active_play_cap();
		update_option( 'cartcue_db_version', CARTCUE_VERSION );
	}

	public static function deactivate(): void {
		flush_rewrite_rules();
		if ( self::action_scheduler_ready() ) {
			self::unschedule_all_hooks();
		} else {
			update_option( self::DEFERRED_UNSCHEDULE_OPTION, '1', false );
		}
	}

	/**
	 * Process a deferred group-wide unschedule after plugin deactivation.
	 */
	public static function maybe_process_deferred_as_cleanup(): void {
		if ( ! get_option( self::DEFERRED_UNSCHEDULE_OPTION ) ) {
			return;
		}
		self::run_when_action_scheduler_ready(
			static function (): void {
				if ( ! get_option( self::DEFERRED_UNSCHEDULE_OPTION ) ) {
					return;
				}
				delete_option( self::DEFERRED_UNSCHEDULE_OPTION );
				self::unschedule_all_hooks();
			}
		);
	}

	/** Whether Action Scheduler's data store is ready for the procedural API. */
	public static function action_scheduler_ready(): bool {
		return class_exists( \ActionScheduler::class ) && \ActionScheduler::is_initialized();
	}

	/**
	 * Run a callback now if Action Scheduler is ready, otherwise on action_scheduler_init.
	 *
	 * @param callable():void $callback Callback.
	 */
	public static function run_when_action_scheduler_ready( callable $callback ): void {
		if ( self::action_scheduler_ready() ) {
			$callback();
			return;
		}
		add_action(
			'action_scheduler_init',
			static function () use ( $callback ): void {
				if ( ! self::action_scheduler_ready() ) {
					return;
				}
				$callback();
			},
			10
		);
	}

	public static function create_tables(): void {
		global $wpdb;
		$cc = $wpdb->get_charset_collate();
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		dbDelta( "CREATE TABLE " . DB::workflows() . " (
			id                  bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			name                varchar(255)        NOT NULL DEFAULT '',
			trigger_slug        varchar(100)        NOT NULL DEFAULT '',
			automation_type     varchar(20)         NOT NULL DEFAULT 'event',
			trigger_settings    longtext            NOT NULL,
			rules               longtext            NOT NULL,
			actions             longtext            NOT NULL,
			sequence            longtext            NULL,
			timing              longtext            NOT NULL,
			status              varchar(20)         NOT NULL DEFAULT 'active',
			run_count           bigint(20) unsigned NOT NULL DEFAULT 0,
			max_runs_per_customer int(10) unsigned  NOT NULL DEFAULT 0,
			cooldown_days       int(10) unsigned    NOT NULL DEFAULT 0,
			send_to_past_matches tinyint(1)         NOT NULL DEFAULT 0,
			campaign_id         bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at          datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			updated_at          datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY trigger_slug (trigger_slug),
			KEY status (status),
			KEY campaign_id (campaign_id)
		) $cc;" );

		if ( defined( 'CARTCUE_PRO_VERSION' ) ) {
			dbDelta( "CREATE TABLE " . DB::campaigns() . " (
				id          bigint(20) unsigned NOT NULL AUTO_INCREMENT,
				name        varchar(255)        NOT NULL DEFAULT '',
				description text                NOT NULL,
				created_at  datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
				updated_at  datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
				PRIMARY KEY (id)
			) $cc;" );
		}

		dbDelta( "CREATE TABLE " . DB::logs() . " (
			id             bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			workflow_id    bigint(20) unsigned NOT NULL DEFAULT 0,
			workflow_name  varchar(255)        NOT NULL DEFAULT '',
			trigger_slug   varchar(100)        NOT NULL DEFAULT '',
			customer_email varchar(200)        NOT NULL DEFAULT '',
			trigger_data   longtext            NOT NULL,
			status         varchar(20)         NOT NULL DEFAULT 'success',
			message        text                NOT NULL,
			created_at     datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY workflow_id (workflow_id),
			KEY status (status),
			KEY created_at (created_at)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::carts() . " (
			id                     bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			user_id                bigint(20) unsigned NOT NULL DEFAULT 0,
			email                  varchar(200)        NOT NULL DEFAULT '',
			session_token          varchar(255)        NOT NULL DEFAULT '',
			cart_contents          longtext            NOT NULL,
			total                  varchar(50)         NOT NULL DEFAULT '0',
			status                 varchar(20)         NOT NULL DEFAULT 'active',
			checkout_started_at    datetime                     DEFAULT NULL,
			checkout_started_fired tinyint(1)          NOT NULL DEFAULT 0,
			created_at             datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			updated_at             datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY user_id (user_id),
			KEY email (email(100)),
			KEY status (status),
			KEY updated_at (updated_at),
			KEY checkout_started_at (checkout_started_at)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::play_runs() . " (
			id             bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			workflow_id    bigint(20) unsigned NOT NULL,
			customer_email varchar(200)        NOT NULL DEFAULT '',
			fired_at       datetime            NOT NULL,
			PRIMARY KEY (id),
			KEY wf_email (workflow_id, customer_email(100)),
			KEY fired_at (fired_at)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::email_tracking() . " (
			id             bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			workflow_id    bigint(20) unsigned NOT NULL DEFAULT 0,
			customer_email varchar(200)        NOT NULL DEFAULT '',
			token          varchar(64)         NOT NULL DEFAULT '',
			subject        varchar(500)        NOT NULL DEFAULT '',
			sent_at        datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			opened_at      datetime                     DEFAULT NULL,
			opens_count    int(10) unsigned    NOT NULL DEFAULT 0,
			clicks         int(10) unsigned    NOT NULL DEFAULT 0,
			PRIMARY KEY (id),
			UNIQUE KEY token (token),
			KEY workflow_id (workflow_id),
			KEY customer_email (customer_email(100)),
			KEY sent_at (sent_at)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::conversions() . " (
			id             bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			workflow_id    bigint(20) unsigned NOT NULL DEFAULT 0,
			customer_email varchar(200)        NOT NULL DEFAULT '',
			order_id       bigint(20) unsigned NOT NULL DEFAULT 0,
			order_total    decimal(10,2)       NOT NULL DEFAULT 0.00,
			converted_at   datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY workflow_id (workflow_id),
			KEY customer_email (customer_email(100)),
			KEY order_id (order_id)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::consents() . " (
			id         bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			email      varchar(200)        NOT NULL,
			status     tinyint(1)          NOT NULL DEFAULT 0,
			source     varchar(50)         NOT NULL DEFAULT '',
			granted_at datetime            NOT NULL,
			PRIMARY KEY (id),
			UNIQUE KEY email (email(100))
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::unsubscribe_reasons() . " (
			id           bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			email        varchar(200)        NOT NULL DEFAULT '',
			reason       varchar(50)         NOT NULL DEFAULT '',
			other_text   varchar(500)        NOT NULL DEFAULT '',
			workflow_id  bigint(20) unsigned NOT NULL DEFAULT 0,
			created_at   datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY email (email(100)),
			KEY created_at (created_at)
		) $cc;" );

		dbDelta( "CREATE TABLE " . DB::journey_events() . " (
			id             bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			customer_email varchar(200)        NOT NULL DEFAULT '',
			workflow_id    bigint(20) unsigned NOT NULL DEFAULT 0,
			event_type     varchar(50)         NOT NULL DEFAULT '',
			detail         varchar(500)        NOT NULL DEFAULT '',
			created_at     datetime            NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (id),
			KEY customer_email (customer_email(100)),
			KEY created_at (created_at)
		) $cc;" );

		MarketingConsent::migrate_guest_consents();
		self::upgrade_schema_columns();
		self::upgrade_email_tracking_columns();
	}

	/**
	 * Add columns introduced after early installs (dbDelta often skips ALTERs).
	 */
	public static function upgrade_schema_columns(): void {
		global $wpdb;

		$table = DB::workflows();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
			return;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$columns = $wpdb->get_col( $wpdb->prepare( 'SHOW COLUMNS FROM %i', $table ), 0 );
		if ( ! is_array( $columns ) ) {
			return;
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		if ( ! in_array( 'send_to_past_matches', $columns, true ) ) {
			$wpdb->query(
				"ALTER TABLE `{$table}` ADD COLUMN send_to_past_matches tinyint(1) NOT NULL DEFAULT 0 AFTER cooldown_days"
			);
			$columns[] = 'send_to_past_matches';
		}

		if ( ! in_array( 'campaign_id', $columns, true ) ) {
			$wpdb->query(
				"ALTER TABLE `{$table}` ADD COLUMN campaign_id bigint(20) unsigned NOT NULL DEFAULT 0 AFTER send_to_past_matches"
			);
		}

		if ( ! in_array( 'sequence', $columns, true ) ) {
			$wpdb->query(
				"ALTER TABLE `{$table}` ADD COLUMN sequence longtext NULL AFTER actions"
			);
		}

		if ( ! in_array( 'automation_type', $columns, true ) ) {
			$wpdb->query(
				"ALTER TABLE `{$table}` ADD COLUMN automation_type varchar(20) NOT NULL DEFAULT 'event' AFTER trigger_slug"
			);
		}

		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
	}

	public static function upgrade_email_tracking_columns(): void {
		global $wpdb;

		$table = DB::email_tracking();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
			return;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$columns = $wpdb->get_col( $wpdb->prepare( 'SHOW COLUMNS FROM %i', $table ), 0 );
		if ( ! is_array( $columns ) ) {
			return;
		}

		if ( ! in_array( 'opens_count', $columns, true ) ) {
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query(
				"ALTER TABLE `{$table}` ADD COLUMN opens_count int(10) unsigned NOT NULL DEFAULT 0 AFTER opened_at"
			);
			$wpdb->query(
				"UPDATE `{$table}` SET opens_count = 1 WHERE opened_at IS NOT NULL AND opens_count = 0"
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		}
	}

	public static function schedule_events(): void {
		if ( ! self::action_scheduler_ready() || ! function_exists( 'as_has_scheduled_action' ) ) {
			return;
		}

		if ( ! as_has_scheduled_action( 'cartcue_process_abandoned_carts', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( time() + 900, 15 * MINUTE_IN_SECONDS, 'cartcue_process_abandoned_carts', [], self::AS_GROUP );
		}
		if ( defined( 'CARTCUE_PRO_VERSION' ) && ! as_has_scheduled_action( 'cartcue_process_checkout_started', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( time() + 600, 15 * MINUTE_IN_SECONDS, 'cartcue_process_checkout_started', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_process_win_back', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'tomorrow 02:00:00' ), DAY_IN_SECONDS, 'cartcue_process_win_back', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_process_never_purchased', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'tomorrow 03:00:00' ), DAY_IN_SECONDS, 'cartcue_process_never_purchased', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_prune_logs', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'next sunday 03:00:00' ), WEEK_IN_SECONDS, 'cartcue_prune_logs', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_prune_carts', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'next sunday 04:00:00' ), WEEK_IN_SECONDS, 'cartcue_prune_carts', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_prune_play_runs', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'next sunday 05:00:00' ), WEEK_IN_SECONDS, 'cartcue_prune_play_runs', [], self::AS_GROUP );
		}
		// cartcue_sequence_step and cartcue_check_pending_order are on-demand only.
	}

	public static function schedule_pro_events(): void {
		if ( ! self::action_scheduler_ready() || ! function_exists( 'as_has_scheduled_action' ) ) {
			return;
		}
		if ( ! as_has_scheduled_action( 'cartcue_weekly_digest', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'next monday 09:00:00' ), WEEK_IN_SECONDS, 'cartcue_weekly_digest', [], self::AS_GROUP );
		}
		if ( ! as_has_scheduled_action( 'cartcue_check_expiring_cards', [], self::AS_GROUP ) ) {
			as_schedule_recurring_action( strtotime( 'tomorrow 04:00:00' ), DAY_IN_SECONDS, 'cartcue_check_expiring_cards', [], self::AS_GROUP );
		}
	}

	/**
	 * Remove Pro-only cron hooks (Lite must not schedule or leave them orphaned).
	 */
	public static function unschedule_pro_only_hooks(): void {
		if ( ! self::action_scheduler_ready() || ! function_exists( 'as_unschedule_all_actions' ) ) {
			return;
		}
		foreach ( self::PRO_ONLY_HOOKS as $hook ) {
			as_unschedule_all_actions( $hook, [], self::AS_GROUP );
		}
	}

	/**
	 * Clear every Action Scheduler job in the CartCue group.
	 */
	public static function unschedule_all_hooks(): void {
		if ( ! self::action_scheduler_ready() || ! function_exists( 'as_unschedule_all_actions' ) ) {
			return;
		}
		as_unschedule_all_actions( '', [], self::AS_GROUP );
	}
}
