<?php
/**
 * Database installer for the background mail queue.
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP\Queue;

defined( 'ABSPATH' ) || exit;

/**
 * Class Installer
 *
 * Handles creation and upgrade of the nimblymail_queue table that stores
 * deferred wp_mail() payloads for the Intelligent Background Queue.
 *
 * @since 1.0.0
 * @since 1.0.6 install_for_blog() now skips a blog id that no longer resolves to an existing site.
 */
class Installer {

    public const DB_VERSION  = '1.0.0';
    public const VERSION_KEY = 'nimblymail_queue_db_version';

    /**
     * Install the queue table for the current blog.
     *
     * @since 1.0.0
     *
     * @return void
     */
    public static function install(): void {
        self::create_table();
        update_option( self::VERSION_KEY, self::DB_VERSION );
    }

    /**
     * Install the queue table for a specific blog on a multisite network.
     *
     * Wraps install() in switch_to_blog/restore_current_blog so $wpdb->prefix
     * resolves to the target blog's prefix. No-op outside multisite.
     *
     * @since 1.0.0
     * @since 1.0.6 Added a get_site() existence check: NetworkActivationInstaller
     *              can now run a network activation's per-site installs across
     *              a much longer wall-clock window than the old synchronous
     *              loop, during which a site could be deleted mid-run.
     *
     * @param int $blog_id Target blog id.
     *
     * @return void
     */
    public static function install_for_blog( int $blog_id ): void {
        if ( $blog_id <= 0 || ! function_exists( 'switch_to_blog' ) ) {
            return;
        }

        if ( function_exists( 'get_site' ) && ! get_site( $blog_id ) ) {
            return;
        }

        switch_to_blog( $blog_id );
        self::install();
        restore_current_blog();
    }

    /**
     * Run the schema upgrade if the stored DB version is older than DB_VERSION.
     *
     * @since 1.0.0
     *
     * @return void
     */
    public static function maybe_upgrade(): void {
        $installed = get_option( self::VERSION_KEY, '0' );
        if ( version_compare( $installed, self::DB_VERSION, '<' ) ) {
            self::create_table();
            update_option( self::VERSION_KEY, self::DB_VERSION );
        }
    }

    /**
     * Create or alter the queue table.
     *
     * Mirrors the dbDelta-driven pattern used by the logs Installer. The
     * table holds encrypted, serialized wp_mail attribute sets pending
     * delivery, plus retry bookkeeping. Successful sends delete their row
     * immediately; permanently failed rows linger for 7 days for audit and
     * are then purged by Log\Rotation.
     *
     * @since 1.0.0
     *
     * @return void
     */
    private static function create_table(): void {
        global $wpdb;

        $charset = $wpdb->get_charset_collate();

        // dbDelta requires the full CREATE TABLE statement as a single string.
        // Table name is interpolated from $wpdb->prefix which is internal,
        // not user-supplied.
        $sql = "CREATE TABLE {$wpdb->prefix}nimblymail_queue (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            hash CHAR(32) NOT NULL DEFAULT '',
            payload LONGTEXT NOT NULL,
            priority TINYINT UNSIGNED NOT NULL DEFAULT 10,
            status VARCHAR(20) NOT NULL DEFAULT 'pending',
            attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
            next_attempt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            last_error TEXT NULL,
            started_at DATETIME NULL,
            failed_at DATETIME NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY idx_status_priority_next (status, priority, next_attempt),
            KEY idx_failed_at (failed_at),
            KEY idx_hash (hash)
        ) {$charset};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

    /**
     * Drop the queue table on uninstall.
     *
     * @since 1.0.0
     *
     * @return void
     */
    public static function uninstall(): void {
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $wpdb->prefix is internal; no user input.
        $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}nimblymail_queue" );
        delete_option( self::VERSION_KEY );
    }
}
