<?php

namespace SoftminalB2B\Services;

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

use SoftminalB2B\Install;

/**
 * Clean uninstall (FEATURES H4).
 *
 * Three levels, safest first:
 *
 *  1. Keep data (default): nothing is removed. Deleting the plugin by accident
 *     must never destroy a merchant's configuration, so the destructive path is
 *     strictly opt-in.
 *  2. Remove data (`smb2b_remove_data_on_uninstall = yes`): every non-financial
 *     trace is deleted — tables, options, user meta, order meta, temp files —
 *     to honour the zero-`smb2b_`-residue promise. Financial records are kept,
 *     because merchants may be legally required to retain accounting data.
 *  3. Also remove financial data (`smb2b_remove_financial_data = yes`, a second
 *     explicit opt-in): the credit ledger and accounts go too — a full wipe.
 *
 * Financial = the append-only credit ledger and the accounts it belongs to.
 */
class Uninstaller
{
    /** Tables that hold accounting records; kept unless a full wipe is chosen. */
    private const FINANCIAL_TABLES = ['credit_transactions', 'credit_accounts'];

    public static function run(): void
    {
        if (get_option('smb2b_remove_data_on_uninstall') !== 'yes') {
            // Keep everything. Leave even the version marker so a reinstall
            // picks up exactly where it left off.
            return;
        }

        $removeFinancial = get_option('smb2b_remove_financial_data') === 'yes';

        self::dropTables($removeFinancial);
        self::deleteUserMeta();
        self::deleteOrderMeta();
        self::deleteFiles();
        self::unschedule();

        // Options last: deleting the group-membership user meta can fire hooks
        // that lazily re-create an option (e.g. the price-cache version), so the
        // option sweep has to be the final step to guarantee zero residue.
        self::deleteOptions();
    }

    private static function dropTables(bool $includeFinancial): void
    {
        global $wpdb;

        $tables = Install::tables();

        if (!$includeFinancial) {
            $tables = array_values(array_diff($tables, self::FINANCIAL_TABLES));
        }

        foreach ($tables as $table) {
            $name = Install::tableName($table);
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $wpdb->query("DROP TABLE IF EXISTS `{$name}`");
        }
    }

    /**
     * Every option this plugin ever wrote is prefixed `smb2b_`, so one pattern
     * delete leaves no orphan behind — the exact residue B2BKing is criticised
     * for.
     */
    private static function deleteOptions(): void
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $names = $wpdb->get_col(
            $wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", 'smb2b\_%')
        );

        foreach ((array) $names as $name) {
            delete_option($name);
        }
    }

    private static function deleteUserMeta(): void
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $keys = $wpdb->get_col(
            $wpdb->prepare("SELECT DISTINCT meta_key FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", 'smb2b\_%')
        );

        foreach ((array) $keys as $key) {
            delete_metadata('user', 0, $key, '', true);
        }
    }

    /**
     * Order-level B2B facts are stored as order meta keyed `_smb2b_`. Under HPOS
     * that lives in the orders-meta table; on legacy stores it is postmeta.
     * Both are cleared so the promise holds regardless of storage mode.
     */
    private static function deleteOrderMeta(): void
    {
        global $wpdb;

        // Legacy postmeta.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query(
            $wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE %s", '\_smb2b\_%')
        );

        // HPOS orders-meta table, if present.
        $hposMeta = $wpdb->prefix . 'wc_orders_meta';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $hposMeta));

        if ($exists === $hposMeta) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $wpdb->query( $wpdb->prepare("DELETE FROM `{$hposMeta}` WHERE meta_key LIKE %s", '\_smb2b\_%') );
        }
    }

    /**
     * Remove the dompdf font cache and any leftover temp invoices.
     */
    private static function deleteFiles(): void
    {
        $upload = wp_upload_dir();

        if (empty($upload['error'])) {
            self::deleteDir(trailingslashit($upload['basedir']) . 'smb2b-fontcache');
        }
    }

    private static function deleteDir(string $dir): void
    {
        if (!is_dir($dir)) {
            return;
        }

        $items = glob(trailingslashit($dir) . '*') ?: [];

        foreach ($items as $item) {
            if (is_dir($item)) {
                self::deleteDir($item);
            } else {
                wp_delete_file($item);
            }
        }

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
        @rmdir($dir);
    }

    /**
     * Cancel any Action Scheduler jobs this plugin queued, so nothing keeps
     * firing against tables that no longer exist.
     */
    private static function unschedule(): void
    {
        if (function_exists('as_unschedule_all_actions')) {
            as_unschedule_all_actions('', [], 'smb2b');
        }
    }
}
