<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Insights;

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

/**
 * Safe migration runner for cPanel/hosted WP-CLI environments where long wp eval payloads are fragile.
 */
final class MigrationRunnerCommand
{
    /**
     * Validate a JSON migration plan.
     *
     * ## OPTIONS
     *
     * --plan=<path>
     * : Absolute or relative path to a JSON migration plan file.
     *
     * ## EXAMPLES
     *
     *     wp h4bs migrate validate --plan=/home/account/migrations/help4-plan.json
     */
    public function validate(array $args, array $assocArgs): void
    {
        $planPath = isset($assocArgs['plan']) ? (string) $assocArgs['plan'] : '';
        $plan = $this->loadPlan($planPath);
        $steps = $this->stepsFromPlan($plan);
        \WP_CLI::success(
            sprintf(
                'Plan is valid. steps=%d name=%s',
                count($steps),
                (string) ($plan['meta']['name'] ?? 'unnamed')
            )
        );
    }

    /**
     * Run a JSON migration plan.
     *
     * ## OPTIONS
     *
     * --plan=<path>
     * : Absolute or relative path to a JSON migration plan file.
     *
     * [--dry-run=<0|1>]
     * : Validate and simulate actions without writing DB changes.
     * ---
     * default: 1
     * ---
     *
     * ## EXAMPLES
     *
     *     wp h4bs migrate run --plan=/home/account/migrations/help4-plan.json --dry-run=1
     *     wp h4bs migrate run --plan=/home/account/migrations/help4-plan.json --dry-run=0
     */
    public function run(array $args, array $assocArgs): void
    {
        $planPath = isset($assocArgs['plan']) ? (string) $assocArgs['plan'] : '';
        $dryRun = ! isset($assocArgs['dry-run']) || (string) $assocArgs['dry-run'] !== '0';
        $plan = $this->loadPlan($planPath);
        $steps = $this->stepsFromPlan($plan);

        $summary = [
            'total' => count($steps),
            'applied' => 0,
            'errors' => 0,
            'actions' => [],
        ];

        foreach ($steps as $idx => $step) {
            $action = sanitize_key((string) ($step['action'] ?? ''));
            if ($action === '') {
                $summary['errors']++;
                \WP_CLI::warning('Step ' . ($idx + 1) . ' missing action.');
                continue;
            }

            $summary['actions'][$action] = (int) ($summary['actions'][$action] ?? 0) + 1;

            try {
                $applied = $this->runStep($action, $step, $dryRun);
                if ($applied) {
                    $summary['applied']++;
                }
            } catch (\Throwable $e) {
                $summary['errors']++;
                \WP_CLI::warning('Step ' . ($idx + 1) . ' [' . $action . '] failed: ' . $e->getMessage());
            }
        }

        $mode = $dryRun ? 'DRY-RUN' : 'APPLY';
        \WP_CLI::log('Mode: ' . $mode);
        \WP_CLI::log('Total steps: ' . (string) $summary['total']);
        \WP_CLI::log('Applied steps: ' . (string) $summary['applied']);
        \WP_CLI::log('Errors: ' . (string) $summary['errors']);
        foreach ((array) $summary['actions'] as $name => $count) {
            \WP_CLI::log(' - ' . (string) $name . ': ' . (string) $count);
        }

        if ((int) $summary['errors'] > 0) {
            \WP_CLI::error('Migration plan completed with errors.');
            return;
        }

        \WP_CLI::success($dryRun ? 'Dry-run completed without errors.' : 'Migration plan applied successfully.');
    }

    /**
     * @return array<string, mixed>
     */
    private function loadPlan(string $planPath): array
    {
        $cleanPath = trim($planPath);
        if ($cleanPath === '') {
            \WP_CLI::error('Missing required --plan argument.');
        }

        $resolved = realpath($cleanPath);
        if ($resolved === false || ! is_file($resolved) || ! is_readable($resolved)) {
            \WP_CLI::error('Plan file not found or unreadable: ' . $cleanPath);
        }

        $extension = strtolower((string) pathinfo($resolved, PATHINFO_EXTENSION));
        if ($extension !== 'json') {
            \WP_CLI::error('Plan file must be JSON (.json).');
        }

        $raw = file_get_contents($resolved);
        if (! is_string($raw) || trim($raw) === '') {
            \WP_CLI::error('Plan file is empty: ' . $resolved);
        }

        $decoded = json_decode($raw, true);
        if (! is_array($decoded)) {
            \WP_CLI::error('Invalid JSON plan: ' . $resolved);
        }

        return $decoded;
    }

    /**
     * @param array<string, mixed> $plan
     * @return array<int, array<string, mixed>>
     */
    private function stepsFromPlan(array $plan): array
    {
        $steps = $plan['steps'] ?? [];
        if (! is_array($steps) || $steps === []) {
            \WP_CLI::error('Plan is missing a non-empty "steps" array.');
        }

        $normalized = [];
        foreach ($steps as $step) {
            if (is_array($step)) {
                $normalized[] = $step;
            }
        }
        if ($normalized === []) {
            \WP_CLI::error('No valid step objects found in plan.');
        }

        return $normalized;
    }

    /**
     * @param array<string, mixed> $step
     */
    private function runStep(string $action, array $step, bool $dryRun): bool
    {
        switch ($action) {
            case 'set_option':
                return $this->stepSetOption($step, $dryRun);
            case 'set_post_meta':
                return $this->stepSetPostMeta($step, $dryRun);
            case 'set_builder_mode':
                return $this->stepSetBuilderMode($step, $dryRun);
            case 'set_builder_layout':
                return $this->stepSetBuilderLayout($step, $dryRun);
            default:
                throw new \RuntimeException('Unsupported action "' . esc_html($action) . '".');
        }
    }

    /**
     * @param array<string, mixed> $step
     */
    private function stepSetOption(array $step, bool $dryRun): bool
    {
        $key = (string) ($step['key'] ?? '');
        $key = trim($key);
        if ($key === '') {
            throw new \RuntimeException('set_option requires "key".');
        }

        if (! str_starts_with($key, 'h4bs_')) {
            \WP_CLI::warning('Option "' . $key . '" is outside h4bs_* namespace.');
        }

        if ($dryRun) {
            return true;
        }

        $value = $step['value'] ?? null;
        update_option($key, $value, false);
        return true;
    }

    /**
     * @param array<string, mixed> $step
     */
    private function stepSetPostMeta(array $step, bool $dryRun): bool
    {
        $postId = (int) ($step['post_id'] ?? 0);
        if ($postId <= 0 || get_post($postId) === null) {
            throw new \RuntimeException('set_post_meta requires valid "post_id".');
        }

        $metaKey = (string) ($step['meta_key'] ?? '');
        $metaKey = trim($metaKey);
        if ($metaKey === '') {
            throw new \RuntimeException('set_post_meta requires "meta_key".');
        }

        if ($dryRun) {
            return true;
        }

        $value = $step['value'] ?? null;
        update_post_meta($postId, $metaKey, $value);
        return true;
    }

    /**
     * @param array<string, mixed> $step
     */
    private function stepSetBuilderMode(array $step, bool $dryRun): bool
    {
        $postId = (int) ($step['post_id'] ?? 0);
        if ($postId <= 0 || get_post($postId) === null) {
            throw new \RuntimeException('set_builder_mode requires valid "post_id".');
        }

        $mode = sanitize_key((string) ($step['mode'] ?? 'help4'));
        if (! in_array($mode, ['help4', 'wp'], true)) {
            $mode = 'help4';
        }

        $enabledRaw = $step['enabled'] ?? 1;
        $enabled = (string) ((int) ((bool) $enabledRaw));

        if ($dryRun) {
            return true;
        }

        update_post_meta($postId, '_h4_builder_mode', $mode);
        update_post_meta($postId, '_h4_builder_enabled', $enabled);
        return true;
    }

    /**
     * @param array<string, mixed> $step
     */
    private function stepSetBuilderLayout(array $step, bool $dryRun): bool
    {
        $postId = (int) ($step['post_id'] ?? 0);
        if ($postId <= 0 || get_post($postId) === null) {
            throw new \RuntimeException('set_builder_layout requires valid "post_id".');
        }

        $layout = $step['layout'] ?? [];
        if (is_string($layout)) {
            $decoded = json_decode($layout, true);
            $layout = is_array($decoded) ? $decoded : [];
        }

        if (! is_array($layout)) {
            throw new \RuntimeException('set_builder_layout requires layout array or JSON string.');
        }

        $json = wp_json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
        if (! is_string($json) || $json === '') {
            throw new \RuntimeException('Failed encoding builder layout payload.');
        }

        if ($dryRun) {
            return true;
        }

        update_post_meta($postId, '_h4_builder_layout', wp_slash($json));
        update_post_meta($postId, '_h4_builder_mode', 'help4');
        update_post_meta($postId, '_h4_builder_enabled', '1');
        return true;
    }
}
