<?php

namespace Pay4AllForMypos\Backend;

use Pay4AllForMypos\Support\Paths;
use Pay4AllForMypos\Support\MigrationRunner;

defined( 'ABSPATH' ) || exit;

/**
 * Handles the schema upgrade for the single custom table shipped by
 * Pay4All for myPOS - `{prefix}pay4all_for_mypos_pending_payments`. Modelled
 * after the Stripe plugin's SchemaMigrator: same admin-notice UX, same
 * nonce-guarded manual-trigger URL, same on-demand + upgrade dbDelta call.
 *
 * The pending_payments table tracks IPC transactions between the moment
 * the customer is redirected to myPOS and the moment the site receives
 * the IPCPurchaseNotify webhook - so a cron job (Batch 3) can reconcile
 * orphaned orders (customer closed the tab, myPOS lost the ping, etc.).
 */
class SchemaMigrator
{
    public static function init()
    {
        add_action( 'wp_ajax_' . PAY4ALL_FOR_MYPOS_PREFIX . '_db_update', __CLASS__ . '::ajax_apply_db_update' );
        add_action( 'admin_notices', __CLASS__ . '::show_update_admin_notice' );
        add_action( 'init', __CLASS__ . '::apply_db_update' );
    }

    /**
     * True when the current admin screen belongs to our plugin (menu pages,
     * the WC gateway settings screen) or to `plugins.php`. Everything else
     * is out-of-scope per WP.org Guideline 11 (Section 18) - no cross-plugin notices.
     */
    private static function is_plugin_screen() : bool
    {
        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen ) { return false; }
        if ( in_array( $screen->id, [ 'plugins', 'plugins-network' ], true ) ) { return true; }
        if ( strpos( (string) $screen->id, 'pay4all-for-mypos' ) !== false ) { return true; }
        // WooCommerce gateway settings tab for our gateway.
        if ( $screen->id === 'woocommerce_page_wc-settings'
             // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen match, no state mutation.
             && isset( $_GET['section'] ) && sanitize_key( wp_unslash( (string) $_GET['section'] ) ) === 'pay4all_for_mypos' ) {
            return true;
        }
        return false;
    }

    /**
     * Displays the DB update notice if there is a newer version.
     * Scoped to plugin screens + plugins.php only (WP.org Section 18).
     */
    public static function show_update_admin_notice()
    {
        if ( ! self::is_plugin_screen() ) { return; }

        $last_db_version = get_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_version' );

        if ( empty( $last_db_version ) ) {
            // Initial setup.
            static::update_db();
            return;
        }

        if ( version_compare( PAY4ALL_FOR_MYPOS_DB_VERSION, $last_db_version, ">" ) ) {

            $progress = get_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_update_progress_' . PAY4ALL_FOR_MYPOS_DB_VERSION );

            if ( ! $progress ) {
                $class   = 'notice notice-warning';
                $message = __( 'Please update the database of the Pay4All for myPOS plugin.', 'pay4all-for-mypos' );
                $url     = wp_nonce_url(
                    add_query_arg( [ PAY4ALL_FOR_MYPOS_PREFIX . '-update' => '1' ], get_admin_url() ),
                    PAY4ALL_FOR_MYPOS_PREFIX . '_update_db'
                );
                printf(
                    '<div id="' . esc_attr( PAY4ALL_FOR_MYPOS_PREFIX ) . '-update-notice" class="%1$s"><p>%2$s</p><a href="' . esc_url( $url ) . '" id="' . esc_attr( PAY4ALL_FOR_MYPOS_PREFIX ) . '-update-db-btn">%3$s</a></div>',
                    esc_attr( $class ),
                    esc_html( $message ),
                    esc_html__( 'Update now', 'pay4all-for-mypos' )
                );

            } elseif ( $progress === 'processing' ) {
                $class   = 'notice notice-info';
                $message = __( 'The Pay4All for myPOS database update is in progress. The update is complete when this message disappears.', 'pay4all-for-mypos' );
                printf( '<div id="' . esc_attr( PAY4ALL_FOR_MYPOS_PREFIX ) . '-update-notice" class="%1$s"><p>%2$s</p></div>', esc_attr( $class ), esc_html( $message ) );
            }
        }
    }

    /**
     * Applies the database update triggered from the admin notice link.
     */
    public static function apply_db_update()
    {
        if ( ! isset( $_GET[ PAY4ALL_FOR_MYPOS_PREFIX . '-update' ] ) ) {
            return;
        }
        if ( ! current_user_can( 'administrator' ) ) {
            return;
        }
        $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) ) : '';
        if ( ! wp_verify_nonce( $nonce, PAY4ALL_FOR_MYPOS_PREFIX . '_update_db' ) ) {
            return;
        }
        self::update_db();
    }

    /**
     * Applies the database update via AJAX request.
     */
    public static function ajax_apply_db_update()
    {
        if ( ! check_ajax_referer( PAY4ALL_FOR_MYPOS_PREFIX . '_update_nonce', 'security' ) ) {
            return;
        }

        self::update_db();
    }

    /**
     * Runs dbDelta for the pending_payments table and any migration steps
     * shipped in `src/migrations/`. Idempotent - a call with the same DB
     * version already stored is a no-op (early-return on the version_compare
     * guard).
     */
    public static function update_db()
    {
        $progress   = get_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_update_progress_' . PAY4ALL_FOR_MYPOS_DB_VERSION );
        $db_version = get_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_version' );

        // First-install path (no stored version). We still want the table to
        // exist even when the option was manually wiped, so we treat "empty"
        // like "outdated" and bypass the version_compare gate.
        $needs_run = ! $progress && ( empty( $db_version ) || version_compare( PAY4ALL_FOR_MYPOS_DB_VERSION, $db_version, '>' ) );
        if ( ! $needs_run ) {
            return;
        }

        flush_rewrite_rules();
        update_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_update_progress_' . PAY4ALL_FOR_MYPOS_DB_VERSION, 'processing' );

        Paths::create_dirs();

        self::create_pending_payments_table();

        MigrationRunner::run( PAY4ALL_FOR_MYPOS_DB_VERSION, (string) $db_version );

        update_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_update_progress_' . PAY4ALL_FOR_MYPOS_DB_VERSION, 'complete' );
        update_option( PAY4ALL_FOR_MYPOS_PREFIX . '_db_version', PAY4ALL_FOR_MYPOS_DB_VERSION );
    }

    /**
     * Creates (or upgrades) the `{prefix}pay4all_for_mypos_pending_payments`
     * table via dbDelta. Called from update_db() only.
     *
     *  - `order_id`   : WC order the IPC redirect was created for.
     *  - `ipc_trnref` : myPOS transaction reference returned on IPC callback
     *                   (nullable - populated only after IPCPurchaseNotify).
     *  - `mode`       : 'test' or 'live' - kept per-row so a mode flip
     *                   doesn't break in-flight reconciliation.
     *  - `checked_at` : last time the reconciliation cron ran an
     *                   IPCTxnRefund/IPCTxnStatus check on this row.
     */
    private static function create_pending_payments_table() : void
    {
        global $wpdb;

        $table   = $wpdb->prefix . 'pay4all_for_mypos_pending_payments';
        $collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE {$table} (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            order_id BIGINT UNSIGNED NOT NULL,
            ipc_trnref VARCHAR(64) NULL,
            mode VARCHAR(10) NOT NULL DEFAULT 'test',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            checked_at DATETIME NULL,
            status VARCHAR(20) NOT NULL DEFAULT 'pending',
            PRIMARY KEY  (id),
            KEY idx_order_id (order_id),
            KEY idx_status_checked (status, checked_at)
        ) {$collate};";

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

    /**
     * Notice is displayed when the update was successful.
     */
    public static function success_notice()
    {
        ?>
        <div class="notice notice-success is-dismissible">
            <p><?php esc_html_e( 'Database update successful.', 'pay4all-for-mypos' ); ?></p>
        </div>
        <?php
    }
}
