<?php

namespace GriffinForms\Includes\Migration;

use GriffinForms\Config;
use GriffinForms\Log;
use GriffinForms\Traits\Singleton;

class MigrationRunner
{
    use Singleton;

    private const JOB_TTL = 6 * HOUR_IN_SECONDS;
    private const CHUNK_SIZE = 2;

    private Config $config;
    private MigrationRegistry $registry;
    private array $source_form_cache = [];

    protected function setData()
    {
        $this->config = Config::getInstance();
        $this->registry = MigrationRegistry::getInstance();
    }

    public function createJob(string $source_slug, array $source_ids, array $options = []): array
    {
        $source_slug = sanitize_key($source_slug);
        $source_ids = array_values(array_filter(array_map('absint', $source_ids)));
        $job_id = $this->generateJobId();

        $state = [
            'job_id' => $job_id,
            'status' => 'running',
            'source' => $source_slug,
            'total' => count($source_ids),
            'processed' => 0,
            'percent' => 0,
            'done' => empty($source_ids),
            'result' => [
                'migrated' => [],
                'skipped' => [],
                'errors' => [],
                'forms' => [],
            ],
            'form_queue' => $source_ids,
            'options' => [
                'import_submissions' => !empty($options['import_submissions']),
            ],
            'created_at' => current_time('mysql', true),
            'updated_at' => current_time('mysql', true),
        ];

        if (empty($source_ids)) {
            $state['status'] = 'done';
            $state['percent'] = 100;
        }

        $this->persistJobState($job_id, $state);
        $this->logJobCreated($source_slug, $source_ids, $state);

        return $state;
    }

    public function advanceJob(string $job_id): array
    {
        $state = $this->getJobState($job_id);
        if (empty($state)) {
            return [];
        }

        if (($state['status'] ?? '') !== 'running') {
            return $state;
        }

        $source = $this->registry->getSource((string) ($state['source'] ?? ''));
        if (!$source instanceof MigrationSourceInterface) {
            $state['status'] = 'error';
            $state['done'] = true;
            $state['result']['errors'][] = [
                'message' => __('Migration source could not be resolved.', 'griffinforms-form-builder'),
            ];
            $this->logMigrationEvent(
                'error',
                0,
                sprintf(
                    __('Migration job %1$s failed because source "%2$s" could not be resolved.', 'griffinforms-form-builder'),
                    (string) ($state['job_id'] ?? ''),
                    (string) ($state['source'] ?? '')
                )
            );
            return $this->persistAndReturn($job_id, $state);
        }

        $queue = array_values((array) ($state['form_queue'] ?? []));
        $batch = array_splice($queue, 0, self::CHUNK_SIZE);

        foreach ($batch as $source_form_id) {
            $source_form_id = absint($source_form_id);
            if ($source_form_id <= 0) {
                continue;
            }

            $this->processFormUnit($source, $source_form_id, $state);
            $state['processed'] = min((int) ($state['processed'] ?? 0) + 1, (int) ($state['total'] ?? 0));
        }

        $state['form_queue'] = $queue;
        $state['percent'] = $this->calculatePercent((int) ($state['processed'] ?? 0), (int) ($state['total'] ?? 0));
        $state['updated_at'] = current_time('mysql', true);

        if (empty($queue)) {
            $state['status'] = !empty($state['result']['errors']) ? 'error' : 'done';
            $state['done'] = true;
            $state['percent'] = 100;
            $this->recordMigratedForms($state);
            $this->logJobCompleted($state);
        }

        return $this->persistAndReturn($job_id, $state);
    }

    public function getJobState(string $job_id): array
    {
        $state = get_transient($this->jobKey($job_id));
        return is_array($state) ? $state : [];
    }

    private function processFormUnit(MigrationSourceInterface $source, int $source_form_id, array &$state): void
    {
        $source_form_title = $this->resolveSourceFormTitle($source, $source_form_id);

        $form_result = $source->importForm($source_form_id, (array) ($state['options'] ?? []));
        $this->mergeResult($state, $form_result);
        $unit_migrated = $form_result->migrated;
        $unit_skipped = $form_result->skipped;
        $unit_errors = $form_result->errors;
        $submissions_migrated = 0;

        $griffinforms_form_id = $form_result->griffinforms_form_id ?? null;
        if (!$form_result->success || !$griffinforms_form_id) {
            if (!$griffinforms_form_id) {
                $missing_id_error = [
                    'message' => sprintf(
                        /* translators: %d is the source form id. */
                        __('Source form %d did not return a GriffinForms form ID.', 'griffinforms-form-builder'),
                        $source_form_id
                    ),
                ];
                $state['result']['errors'][] = $missing_id_error;
                $unit_errors[] = $missing_id_error;
            }
            $state['result']['forms'][] = [
                'source_form_id' => $source_form_id,
                'source_form_title' => $source_form_title,
                'griffinforms_form_id' => $griffinforms_form_id ? absint($griffinforms_form_id) : null,
                'status' => 'error',
                'submissions_migrated' => 0,
                'migrated' => $unit_migrated,
                'skipped' => $unit_skipped,
                'errors' => $unit_errors,
            ];
            $this->logMigrationEvent(
                'error',
                $griffinforms_form_id ? absint($griffinforms_form_id) : 0,
                sprintf(
                    __('Failed to import %1$s form #%2$d (%3$s).', 'griffinforms-form-builder'),
                    $source->getLabel(),
                    $source_form_id,
                    $source_form_title !== '' ? $source_form_title : __('Untitled', 'griffinforms-form-builder')
                )
            );
            return;
        }

        if (!empty($state['options']['import_submissions']) && $source->supportsSubmissions()) {
            $submission_options = array_merge((array) ($state['options'] ?? []), ['field_id_map' => $form_result->field_id_map]);
            $submission_result = $source->importSubmissions($source_form_id, absint($griffinforms_form_id), $submission_options);
            $this->mergeResult($state, $submission_result);
            $unit_migrated = array_merge($unit_migrated, $submission_result->migrated);
            $unit_skipped = array_merge($unit_skipped, $submission_result->skipped);
            $unit_errors = array_merge($unit_errors, $submission_result->errors);
            $submissions_migrated = count(array_filter($submission_result->migrated, static fn($item): bool => (($item['type'] ?? '') === 'submission')));
        }

        $submit_reason = __('GriffinForms uses its own submit button.', 'griffinforms-form-builder');
        $non_trivial_skipped = array_filter($unit_skipped, static fn(array $s): bool => ($s['reason'] ?? '') !== $submit_reason);
        $form_status = !empty($unit_errors) ? 'error' : (!empty($non_trivial_skipped) ? 'partial' : 'migrated');
        $state['result']['forms'][] = [
            'source_form_id' => $source_form_id,
            'source_form_title' => $source_form_title,
            'griffinforms_form_id' => absint($griffinforms_form_id),
            'status' => $form_status,
            'submissions_migrated' => $submissions_migrated,
            'migrated' => $unit_migrated,
            'skipped' => $unit_skipped,
            'errors' => $unit_errors,
        ];
        $this->logFormResult(
            $source,
            $source_form_id,
            $source_form_title,
            absint($griffinforms_form_id),
            $form_status,
            $submissions_migrated,
            count($unit_skipped),
            count($unit_errors)
        );
    }

    private function mergeResult(array &$state, MigrationResult $result): void
    {
        $state['result']['migrated'] = array_merge((array) ($state['result']['migrated'] ?? []), $result->migrated);
        $state['result']['skipped'] = array_merge((array) ($state['result']['skipped'] ?? []), $result->skipped);
        $state['result']['errors'] = array_merge((array) ($state['result']['errors'] ?? []), $result->errors);
    }

    private function calculatePercent(int $processed, int $total): int
    {
        if ($total <= 0) {
            return 100;
        }

        return min(100, max(0, (int) floor(($processed / $total) * 100)));
    }

    private function persistAndReturn(string $job_id, array $state): array
    {
        $this->persistJobState($job_id, $state);
        return $state;
    }

    private function persistJobState(string $job_id, array $state): void
    {
        set_transient($this->jobKey($job_id), $state, self::JOB_TTL);
    }

    private function jobKey(string $job_id): string
    {
        return 'gf_migration_job_' . sanitize_key($job_id);
    }

    private function generateJobId(): string
    {
        try {
            return 'mig_' . bin2hex(random_bytes(8));
        } catch (\Throwable $throwable) {
            return 'mig_' . substr(md5((string) microtime(true) . '|' . wp_rand()), 0, 16);
        }
    }

    private function resolveSourceFormTitle(MigrationSourceInterface $source, int $source_form_id): string
    {
        $slug = $source->getSlug();
        if (!isset($this->source_form_cache[$slug])) {
            $map = [];
            foreach ((array) $source->getForms() as $form) {
                $form_id = absint($form['source_id'] ?? 0);
                if ($form_id <= 0) {
                    continue;
                }
                $map[$form_id] = sanitize_text_field((string) ($form['title'] ?? ''));
            }
            $this->source_form_cache[$slug] = $map;
        }

        return sanitize_text_field((string) ($this->source_form_cache[$slug][$source_form_id] ?? ''));
    }

    private function logJobCreated(string $source_slug, array $source_ids, array $state): void
    {
        $this->logMigrationEvent(
            'info',
            0,
            sprintf(
                __('Started migration job %1$s for source "%2$s" with %3$d form(s).', 'griffinforms-form-builder'),
                (string) ($state['job_id'] ?? ''),
                $source_slug,
                count($source_ids)
            )
        );
    }

    private function recordMigratedForms(array $state): void
    {
        $source_slug = sanitize_key((string) ($state['source'] ?? ''));
        if ($source_slug === '') {
            return;
        }

        $migrated_ids = [];
        foreach ((array) ($state['result']['forms'] ?? []) as $form) {
            $status = (string) ($form['status'] ?? '');
            if (in_array($status, ['migrated', 'partial'], true)) {
                $id = absint($form['source_form_id'] ?? 0);
                if ($id > 0) {
                    $migrated_ids[] = $id;
                }
            }
        }

        if (!empty($migrated_ids)) {
            MigrationNotice::getInstance()->recordImported($source_slug, $migrated_ids);
        }
    }

    private function logJobCompleted(array $state): void
    {
        $log_type = !empty($state['result']['errors']) ? 'warning' : 'success';
        $this->logMigrationEvent(
            $log_type,
            0,
            sprintf(
                __('Migration job %1$s finished with status %2$s. Processed %3$d of %4$d form(s).', 'griffinforms-form-builder'),
                (string) ($state['job_id'] ?? ''),
                (string) ($state['status'] ?? 'done'),
                absint($state['processed'] ?? 0),
                absint($state['total'] ?? 0)
            )
        );
    }

    private function logFormResult(
        MigrationSourceInterface $source,
        int $source_form_id,
        string $source_form_title,
        int $griffinforms_form_id,
        string $status,
        int $submissions_migrated,
        int $skipped_count,
        int $error_count
    ): void {
        $status = sanitize_key($status);
        $log_type = match ($status) {
            'migrated' => 'success',
            'partial', 'skipped' => 'warning',
            'error' => 'error',
            default => 'info',
        };

        $this->logMigrationEvent(
            $log_type,
            $griffinforms_form_id,
            sprintf(
                __('Imported %1$s form #%2$d (%3$s) into GriffinForms form #%4$d with status %5$s. Submissions migrated: %6$d. Skipped items: %7$d. Errors: %8$d.', 'griffinforms-form-builder'),
                $source->getLabel(),
                $source_form_id,
                $source_form_title !== '' ? $source_form_title : __('Untitled', 'griffinforms-form-builder'),
                $griffinforms_form_id,
                $status,
                $submissions_migrated,
                $skipped_count,
                $error_count
            )
        );
    }

    private function logMigrationEvent(string $log_type, int $item_id, string $message): void
    {
        Log::getInstance()->add(
            strtolower($log_type),
            'form',
            absint($item_id),
            get_current_user_id(),
            $message,
            'import'
        );
    }
}
