<?php
namespace Pay4All\Pro;

use Pay4All\Pro\License\Cron as LicenseCron;
use Pay4All\Pro\License\LicenseManager;

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

/**
 * Activation / deactivation hooks. Owns:
 *   - the two custom tables restored from pay4all-form's 1.x schema,
 *   - the cron event that drives abandoned-cart recovery e-mails,
 *   - the first-time SaaS handshake that lets pay4all.net anchor the
 *     30-day trial on the domain (see License\LicenseManager doc).
 */
final class Activator {

	public const CRON_HOOK_ABANDONED  = 'p4all_pro_abandoned_cart_tick';
	public const CRON_HOOK_NEWSLETTER = 'p4all_pro_newsletter_tick';

	public static function activate() : void {
		PostTypes::do_register();
		self::install_tickets_table();
		self::install_abandoned_carts_table();
		self::install_newsletter_send_table();
		// Cron drives the abandoned-cart recovery mailer + the 90-day purge.
		// Idempotent — schedule_cron() skips the event when already queued.
		self::schedule_cron();
		// 48 h licence recheck cron. Kept separate from the abandoned-cart
		// event so removing one plugin feature never unschedules the other.
		LicenseCron::ensure_scheduled();
		// The legacy LOCAL trial anchor is retired : delete it defensively
		// on every activate() so an upgrader from an older pay4all-pro
		// version can't keep a bogus start-date in wp_options. The
		// authoritative anchor now lives on pay4all.net (domains.trial_started_at).
		delete_option( 'p4all_pro_trial_started_at' );
		// First-boot handshake with pay4all.net : registers the installation
		// and immediately runs a /check so the server stamps the trial
		// anchor. Silent failure — the 48 h cron and the "Vérifier maintenant"
		// button will retry.
		self::first_boot_handshake();
		flush_rewrite_rules();
	}

	public static function deactivate() : void {
		self::unschedule_cron();
		LicenseCron::unschedule();
	}

	private static function first_boot_handshake() : void {
		try {
			$manager = LicenseManager::instance();
			$reg = $manager->register_installation();
			if ( ! empty( $reg['ok'] ) ) {
				$manager->run_periodic_check( true );
			}
		} catch ( \Throwable $e ) {
			// Never let a network hiccup break the activation flow.
			// wp_options stay consistent ; the next cron will retry.
		}
	}

	public static function install_tickets_table() : void {
		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		$table   = $wpdb->prefix . 'p4all_tickets';
		$charset = $wpdb->get_charset_collate();
		$sql = "CREATE TABLE {$table} (
			id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			order_id BIGINT(20) UNSIGNED NOT NULL,
			product_id BIGINT(20) UNSIGNED NOT NULL,
			product_slug VARCHAR(200) NOT NULL DEFAULT '',
			token CHAR(40) NOT NULL,
			customer_data LONGTEXT NULL,
			validated_at DATETIME NULL,
			validated_ip VARCHAR(45) NOT NULL DEFAULT '',
			created_at DATETIME NOT NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY token (token),
			KEY order_id (order_id),
			KEY product_id (product_id),
			KEY validated_at (validated_at)
		) {$charset};";
		dbDelta( $sql );
	}

	public static function install_abandoned_carts_table() : void {
		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		$table   = $wpdb->prefix . 'p4all_abandoned_carts';
		$charset = $wpdb->get_charset_collate();
		$sql = "CREATE TABLE {$table} (
			id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			form_id BIGINT(20) UNSIGNED NOT NULL,
			email VARCHAR(190) NOT NULL DEFAULT '',
			token CHAR(40) NOT NULL,
			customer_data LONGTEXT NULL,
			items LONGTEXT NULL,
			delivery_id BIGINT(20) UNSIGNED NULL,
			payment_method VARCHAR(60) NOT NULL DEFAULT '',
			subtotal_cents INT UNSIGNED NOT NULL DEFAULT 0,
			delivery_cents INT UNSIGNED NOT NULL DEFAULT 0,
			total_cents INT UNSIGNED NOT NULL DEFAULT 0,
			currency CHAR(3) NOT NULL DEFAULT 'CHF',
			lang VARCHAR(8) NOT NULL DEFAULT '',
			client_ip VARCHAR(45) NOT NULL DEFAULT '',
			user_agent TEXT NULL,
			created_at DATETIME NOT NULL,
			last_updated_at DATETIME NOT NULL,
			email_sent_at DATETIME NULL,
			recovered_at DATETIME NULL,
			converted_to_order_id BIGINT(20) UNSIGNED NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY token (token),
			UNIQUE KEY form_email (form_id, email),
			KEY created_at (created_at),
			KEY email_sent_at (email_sent_at)
		) {$charset};";
		dbDelta( $sql );
	}

	/**
	 * Idempotent schema migration triggered at every boot (see
	 * Plugin::boot_form_mode). dbDelta's ALTER TABLE detection kicks in
	 * only when it sees new columns compared to the live schema, so this
	 * costs a single « SHOW COLUMNS » round-trip when the plugin file is
	 * already in sync. Kept separate from the activate() hook so file-copy
	 * upgrades (which never re-fire activation) still get the new columns.
	 */
	public static function maybe_migrate_schema() : void {
		global $wpdb;
		$flag_option = 'p4all_pro_schema_version';
		$current     = (string) get_option( $flag_option, '' );
		// Bump this string on every schema change ; version comparison is
		// intentional string equality — enough for our sequential releases.
		$latest = '2.6.0-nl-stats';
		if ( $current === $latest ) { return; }
		self::install_newsletter_send_table();
		update_option( $flag_option, $latest, false );
	}

	public static function schedule_cron() : void {
		if ( ! wp_next_scheduled( self::CRON_HOOK_ABANDONED ) ) {
			wp_schedule_event( time() + 300, 'hourly', self::CRON_HOOK_ABANDONED );
		}
		// Newsletter tick uses our custom 'p4all_pro_newsletter' schedule
		// (period = merchant setting, default 15 min). Rescheduled on
		// settings change by NewsletterSpooler::reschedule().
		if ( ! wp_next_scheduled( self::CRON_HOOK_NEWSLETTER ) ) {
			wp_schedule_event( time() + 300, 'p4all_pro_newsletter', self::CRON_HOOK_NEWSLETTER );
		}
	}

	public static function unschedule_cron() : void {
		$ts = wp_next_scheduled( self::CRON_HOOK_ABANDONED );
		if ( $ts ) { wp_unschedule_event( $ts, self::CRON_HOOK_ABANDONED ); }
		$ts_nl = wp_next_scheduled( self::CRON_HOOK_NEWSLETTER );
		if ( $ts_nl ) { wp_unschedule_event( $ts_nl, self::CRON_HOOK_NEWSLETTER ); }
	}

	/**
	 * Log table for newsletter sends. One row per (campaign, customer)
	 * successful send OR failure — used by the spooler to skip already-
	 * processed customers on re-runs, and by the admin page to display the
	 * progress bar + error diagnostics. `status`:
	 *   sent   — delivered to wp_mail without exception
	 *   failed — wp_mail returned false OR threw
	 *   test   — admin preview / test dispatch (not counted in progress)
	 */
	public static function install_newsletter_send_table() : void {
		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		$table   = $wpdb->prefix . 'p4all_newsletter_send';
		$charset = $wpdb->get_charset_collate();
		// The 6 « tracking » columns (track_token → unsubscribed_at) were
		// added in Pro 2.6.0 to power the per-campaign statistics page.
		// dbDelta ALTERs the table in place on upgrade, so existing rows
		// keep working with NULL trackers until the next send re-writes them.
		$sql = "CREATE TABLE {$table} (
			id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			campaign_id BIGINT(20) UNSIGNED NOT NULL,
			customer_id BIGINT(20) UNSIGNED NOT NULL,
			email VARCHAR(190) NOT NULL DEFAULT '',
			lang VARCHAR(8) NOT NULL DEFAULT '',
			status VARCHAR(20) NOT NULL DEFAULT 'sent',
			error TEXT NULL,
			sent_at DATETIME NOT NULL,
			track_token CHAR(40) NOT NULL DEFAULT '',
			opened_at DATETIME NULL,
			open_count INT UNSIGNED NOT NULL DEFAULT 0,
			clicked_at DATETIME NULL,
			click_count INT UNSIGNED NOT NULL DEFAULT 0,
			unsubscribed_at DATETIME NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY campaign_customer (campaign_id, customer_id),
			UNIQUE KEY track_token (track_token),
			KEY campaign_id (campaign_id),
			KEY email (email)
		) {$charset};";
		dbDelta( $sql );
	}
}
