<?php

namespace GriffinForms\Includes\Migration\Sources;

use GriffinForms\Includes\Migration\AbstractMigrationSource;
use GriffinForms\Includes\Migration\MigrationResult;
use GriffinForms\Includes\ImportExport\FormExport;
use GriffinForms\Includes\ImportExport\FormImport;
use GriffinForms\Includes\Pipelines\SubmissionHistoryManager;
use GriffinForms\Includes\Helpers\Compliance;

class ContactForm7Source extends AbstractMigrationSource
{
    protected string $slug = 'contact-form-7';
    protected string $label = 'Contact Form 7';

    public function isInstalled(): bool
    {
        return post_type_exists('wpcf7_contact_form');
    }

    public function getForms(): array
    {
        if (!$this->isInstalled() || !class_exists('\WPCF7_ContactForm')) {
            return [];
        }

        $forms = \WPCF7_ContactForm::find([
            'post_status' => 'publish',
            'orderby' => 'ID',
            'order' => 'ASC',
        ]);

        $items = [];

        foreach ($forms as $form) {
            if (!is_object($form) || !method_exists($form, 'scan_form_tags')) {
                continue;
            }

            $tags = $form->scan_form_tags();
            $items[] = [
                'source_id' => absint($form->id()),
                'title' => sanitize_text_field((string) $form->title()),
                'field_count' => is_array($tags) ? count($tags) : 0,
            ];
        }

        return $items;
    }

    public function importForm($source_id, array $options = []): MigrationResult
    {
        $result = new MigrationResult();
        $source_id = absint($source_id);

        if ($source_id <= 0) {
            return $result->addError(__('Invalid Contact Form 7 form ID.', 'griffinforms-form-builder'));
        }

        $form = $this->getContactForm($source_id);
        if (!$form) {
            return $result->addError(__('Contact Form 7 form not found.', 'griffinforms-form-builder'));
        }

        $post = get_post($source_id);
        if (!$post instanceof \WP_Post) {
            return $result->addError(__('Contact Form 7 post record could not be loaded.', 'griffinforms-form-builder'));
        }

        $tags = $this->scanTags($form);
        if (empty($tags)) {
            return $result->addError(__('Contact Form 7 form has no scannable tags.', 'griffinforms-form-builder'));
        }

        $labels_by_index = $this->extractLabelsByTagOrder((string) $post->post_content);
        $field_entries = [];
        $field_index = 0;

        foreach ($tags as $tag) {
            if (!$tag instanceof \WPCF7_FormTag) {
                continue;
            }

            $field_label = $labels_by_index[$field_index] ?? $this->fallbackLabel($tag);
            $field_index++;

            $mapped_field = $this->mapTagToField($tag, $field_label, $result);
            if (empty($mapped_field)) {
                continue;
            }

            $field_entries[] = $mapped_field;
        }

        if (empty($field_entries)) {
            return $result->addError(__('No Contact Form 7 fields could be mapped into GriffinForms.', 'griffinforms-form-builder'));
        }

        $form_data = $this->buildFormImportPayload($form, $field_entries);
        $importer = new FormImport($form_data);
        $griffinforms_form_id = $importer->importForm(false);

        if (!$griffinforms_form_id) {
            return $result->addError(__('GriffinForms could not import the Contact Form 7 form.', 'griffinforms-form-builder'));
        }

        $this->finalizeImportedForm($griffinforms_form_id, $source_id, (string) $post->post_title);

        foreach ($field_entries as $field_entry) {
            $result->addMigrated((string) ($field_entry['heading'] ?? ''), (string) ($field_entry['field_type'] ?? ''));
        }

        if ($this->detectConditionalLogic((string) $post->post_content)) {
            $result->addSkipped(__('Conditional logic', 'griffinforms-form-builder'), __('Conditional logic not migrated.', 'griffinforms-form-builder'));
        }

        return $result->setGriffinFormsFormId((int) $griffinforms_form_id);
    }

    public function supportsSubmissions(): bool
    {
        global $wpdb;

        $table = $this->getCfdb7TableName();
        $found = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));

        return $found === $table;
    }

    public function importSubmissions($source_form_id, int $griffinforms_form_id, array $options = []): MigrationResult
    {
        global $wpdb;

        $result = new MigrationResult();
        $source_form_id = absint($source_form_id);
        $griffinforms_form_id = absint($griffinforms_form_id);

        if ($source_form_id <= 0 || $griffinforms_form_id <= 0) {
            return $result->addError(__('Invalid Contact Form 7 submission migration request.', 'griffinforms-form-builder'));
        }

        if (!$this->supportsSubmissions()) {
            return $result->addError(__('CFDB7 submission storage is not available on this site.', 'griffinforms-form-builder'));
        }

        $form = $this->getContactForm($source_form_id);
        if (!$form) {
            return $result->addError(__('Contact Form 7 form not found for submission migration.', 'griffinforms-form-builder'));
        }

        $table = $this->getCfdb7TableName();
        $form_title = sanitize_text_field((string) $form->title());
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT submit_time, form_name, field_name, field_value FROM {$table} WHERE form_name = %s ORDER BY submit_time ASC",
                $form_title
            ),
            ARRAY_A
        );

        if (!is_array($rows) || empty($rows)) {
            return $result;
        }

        $grouped_rows = [];
        foreach ($rows as $row) {
            $submit_time = sanitize_text_field((string) ($row['submit_time'] ?? ''));
            if ($submit_time === '') {
                continue;
            }
            $grouped_rows[$submit_time][] = $row;
        }

        if (empty($grouped_rows)) {
            return $result;
        }

        $form_export = new FormExport($griffinforms_form_id);
        $form_data = $form_export->getFormData();
        $field_map = $this->buildImportedFieldMap(is_array($form_data) ? $form_data : []);
        $form_row = $wpdb->get_row($wpdb->prepare("SELECT * FROM %i WHERE id = %d LIMIT 1", $this->config->getTable('form'), $griffinforms_form_id), ARRAY_A) ?: [];
        $migrated_count = 0;

        foreach ($grouped_rows as $submit_time => $submission_rows) {
            $submission_payload = $this->buildSubmissionPayload($submission_rows, $field_map, $result);
            if (empty($submission_payload)) {
                continue;
            }

            $submission_id = $this->insertSubmission($griffinforms_form_id, $submission_payload, $form_row, $submit_time);
            if ($submission_id <= 0) {
                $result->addError(__('A Contact Form 7 submission could not be written into GriffinForms.', 'griffinforms-form-builder'));
                continue;
            }

            $result->addMigrated($submit_time, 'submission');
            $migrated_count++;
        }

        if ($migrated_count > 0) {
            $this->incrementLifetimeSubmissionCount($griffinforms_form_id, $migrated_count);
        }

        return $result;
    }

    private function getContactForm(int $source_id): ?\WPCF7_ContactForm
    {
        if (!class_exists('\WPCF7_ContactForm')) {
            return null;
        }

        $form = \WPCF7_ContactForm::get_instance($source_id);
        return $form instanceof \WPCF7_ContactForm ? $form : null;
    }

    /**
     * @return array<int, \WPCF7_FormTag>
     */
    private function scanTags(\WPCF7_ContactForm $form): array
    {
        $tags = $form->scan_form_tags();
        return is_array($tags) ? array_values($tags) : [];
    }

    /**
     * @return array<int, string>
     */
    private function extractLabelsByTagOrder(string $content): array
    {
        $labels = [];
        $lines = preg_split('/\R/', $content) ?: [];

        foreach ($lines as $line) {
            if (strpos($line, '[') === false) {
                continue;
            }

            preg_match_all('/\[(?!\/)([^\]]+)\]/', $line, $matches);
            if (empty($matches[0])) {
                continue;
            }

            foreach ($matches[0] as $shortcode) {
                $line_label = trim(wp_strip_all_tags(str_replace($shortcode, '', $line)));
                $line_label = preg_replace('/\s+/', ' ', (string) $line_label);
                $labels[] = $line_label !== '' ? $line_label : '';
            }
        }

        return $labels;
    }

    /**
     * @return array<string, mixed>
     */
    private function mapTagToField(\WPCF7_FormTag $tag, string $field_label, MigrationResult $result): array
    {
        $basetype = sanitize_key((string) ($tag->basetype ?? ''));
        $field_label = $field_label !== '' ? $field_label : $this->fallbackLabel($tag);
        $field_name = sanitize_text_field((string) ($tag->name ?? $field_label));
        $is_required = method_exists($tag, 'is_required') && $tag->is_required() ? 1 : 0;

        $type_map = [
            'text' => 'inputtext',
            'email' => 'inputemail',
            'url' => 'inputurl',
            'tel' => 'inputphone',
            'number' => 'inputnumber',
            'date' => 'inputdate',
            'textarea' => 'textarea',
            'select' => 'selectfield',
            'checkbox' => 'inputcheckbox',
            'radio' => 'inputradio',
            'file' => 'fileupload',
            'acceptance' => 'termsconditions',
        ];

        if (!isset($type_map[$basetype])) {
            $reason_map = [
                'submit' => __('GriffinForms uses its own submit button.', 'griffinforms-form-builder'),
                'hidden' => __('No GriffinForms hidden field equivalent is available in this release.', 'griffinforms-form-builder'),
                'quiz' => __('No GriffinForms quiz field equivalent is available in this release.', 'griffinforms-form-builder'),
                'captcha' => __('GriffinForms uses its own CAPTCHA system.', 'griffinforms-form-builder'),
            ];
            $result->addSkipped($field_label, $reason_map[$basetype] ?? __('Unsupported Contact Form 7 tag type.', 'griffinforms-form-builder'));
            return [];
        }

        $field = [
            'field_type' => $type_map[$basetype],
            'name' => $field_name,
            'heading' => sanitize_text_field($field_label),
            'description' => '',
            'is_required' => $is_required,
            'placeholder' => sanitize_text_field((string) ($tag->get_option('placeholder', '', true) ?: '')),
            'field_options' => [],
            'field_settings' => [],
            'validations' => [],
        ];

        if ($basetype === 'select') {
            $field['field_options'] = $this->buildSelectOptions($tag);
        } elseif ($basetype === 'checkbox' || $basetype === 'radio') {
            $field['field_options'] = $this->buildChoiceOptions($tag);
        } elseif ($basetype === 'acceptance') {
            $field['field_options'] = [[__('I agree', 'griffinforms-form-builder') => 'agree']];
        } elseif ($basetype === 'number') {
            $min = $tag->get_option('min', 'signed_num', true);
            $max = $tag->get_option('max', 'signed_num', true);
            if ($min !== false || $max !== false) {
                $range_parts = [];
                if ($min !== false) {
                    $range_parts[] = sprintf(__('Min: %s', 'griffinforms-form-builder'), $min);
                }
                if ($max !== false) {
                    $range_parts[] = sprintf(__('Max: %s', 'griffinforms-form-builder'), $max);
                }
                $result->addSkipped($field_label, implode(', ', $range_parts));
            }
        } elseif ($basetype === 'file') {
            $result->addSkipped($field_label, __('Field mapped to File Upload. Existing uploaded files are not migrated.', 'griffinforms-form-builder'));
        }

        return $field;
    }

    /**
     * @param array<int, array<string, mixed>> $field_entries
     * @return array<string, mixed>
     */
    private function buildFormImportPayload(\WPCF7_ContactForm $form, array $field_entries): array
    {
        $rows = [];

        foreach ($field_entries as $field_entry) {
            $rows[] = [
                'name' => sanitize_text_field((string) ($field_entry['heading'] ?? 'Row')),
                'heading' => '',
                'description' => '',
                'columns' => [
                    [
                        'name' => sanitize_text_field((string) ($field_entry['heading'] ?? 'Column')),
                        'heading' => '',
                        'description' => '',
                        'width' => 12,
                        'field_data' => [$field_entry],
                    ],
                ],
            ];
        }

        return [
            'name' => sanitize_text_field((string) $form->title()),
            'heading' => sanitize_text_field((string) $form->title()),
            'description' => '',
            'autofill' => 0,
            'limit_entries' => [],
            'progress_bar' => [],
            'access_level' => [],
            'submission_edit' => [],
            'user_action' => [
                'type' => 'do_nothing',
                'message' => '',
                'url' => '',
            ],
            'alert_emails' => [get_option('admin_email')],
            'save_entries' => 'always',
            'autoresponder' => '',
            'integration_settings' => [],
            'pages' => [
                [
                    'name' => sanitize_text_field((string) $form->title()),
                    'heading' => sanitize_text_field((string) $form->title()),
                    'description' => '',
                    'step_data' => [
                        'step_name' => '',
                        'step_icon' => '',
                    ],
                    'rows' => $rows,
                ],
            ],
        ];
    }

    private function finalizeImportedForm(int $griffinforms_form_id, int $source_form_id, string $source_title): void
    {
        global $wpdb;

        $table = $this->config->getTable('form');

        $wpdb->update(
            $table,
            [
                'origin' => 'cf7',
                'last_editor' => get_current_user_id(),
                'last_edited' => current_time('mysql', true),
            ],
            ['id' => $griffinforms_form_id],
            ['%s', '%d', '%s'],
            ['%d']
        );
    }

    private function detectConditionalLogic(string $content): bool
    {
        return (bool) preg_match('/\[group\b|\[\/group\]|\bwpcf7cf\b/i', $content);
    }

    private function fallbackLabel(\WPCF7_FormTag $tag): string
    {
        $name = sanitize_text_field((string) ($tag->name ?? ''));
        if ($name !== '') {
            return ucwords(str_replace(['-', '_'], ' ', $name));
        }

        return ucwords(str_replace(['-', '_'], ' ', (string) ($tag->basetype ?? 'Field')));
    }

    private function buildSelectOptions(\WPCF7_FormTag $tag): array
    {
        $options = [];
        $values = is_array($tag->values ?? null) ? $tag->values : [];
        $labels = is_array($tag->labels ?? null) ? $tag->labels : [];

        foreach ($values as $index => $value) {
            $label = sanitize_text_field((string) ($labels[$index] ?? $value));
            $options[$label] = sanitize_title((string) $value);
        }

        return ['' => $options];
    }

    private function buildChoiceOptions(\WPCF7_FormTag $tag): array
    {
        $options = [];
        $values = is_array($tag->values ?? null) ? $tag->values : [];
        $labels = is_array($tag->labels ?? null) ? $tag->labels : [];

        foreach ($values as $index => $value) {
            $label = sanitize_text_field((string) ($labels[$index] ?? $value));
            $options[] = [$label => sanitize_title((string) $value)];
        }

        return $options;
    }

    private function getCfdb7TableName(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'cf7_submitted_data';
    }

    /**
     * @param array<string, mixed> $form_data
     * @return array<string, array<string, mixed>>
     */
    private function buildImportedFieldMap(array $form_data): array
    {
        $field_map = [];

        foreach ((array) ($form_data['pages'] ?? []) as $page) {
            $page_id = absint($page['id'] ?? 0);
            foreach ((array) ($page['rows'] ?? []) as $row) {
                foreach ((array) ($row['columns'] ?? []) as $column) {
                    foreach ((array) ($column['field_data'] ?? []) as $field) {
                        $field_id = absint($field['id'] ?? 0);
                        if ($field_id <= 0 || $page_id <= 0) {
                            continue;
                        }

                        $normalized_heading = $this->normalizeLookupKey((string) ($field['heading'] ?? ''));
                        $normalized_name = $this->normalizeLookupKey((string) ($field['name'] ?? ''));
                        $entry = [
                            'field_id' => $field_id,
                            'page_id' => $page_id,
                            'field_type' => sanitize_key((string) ($field['field_type'] ?? '')),
                            'heading' => sanitize_text_field((string) ($field['heading'] ?? '')),
                            'name' => sanitize_text_field((string) ($field['name'] ?? '')),
                        ];

                        if ($normalized_heading !== '') {
                            $field_map[$normalized_heading] = $entry;
                        }
                        if ($normalized_name !== '') {
                            $field_map[$normalized_name] = $entry;
                        }
                    }
                }
            }
        }

        return $field_map;
    }

    /**
     * @param array<int, array<string, mixed>> $submission_rows
     * @param array<string, array<string, mixed>> $field_map
     * @return array<string, array<int|string, mixed>>
     */
    private function buildSubmissionPayload(array $submission_rows, array $field_map, MigrationResult $result): array
    {
        $payload = [];

        foreach ($submission_rows as $row) {
            $field_name = $this->normalizeLookupKey((string) ($row['field_name'] ?? ''));
            if ($field_name === '') {
                continue;
            }

            $field_meta = $field_map[$field_name] ?? null;
            if (!is_array($field_meta)) {
                $result->addSkipped((string) ($row['field_name'] ?? ''), __('No matching GriffinForms field.', 'griffinforms-form-builder'));
                continue;
            }

            $page_key = 'page_' . absint($field_meta['page_id'] ?? 0);
            if ($page_key === 'page_0') {
                continue;
            }

            $field_id = absint($field_meta['field_id'] ?? 0);
            if ($field_id <= 0) {
                continue;
            }

            $field_type = sanitize_key((string) ($field_meta['field_type'] ?? ''));
            $payload[$page_key][$field_id] = $this->normalizeSubmissionValue($field_type, (string) ($row['field_value'] ?? ''));
        }

        return $payload;
    }

    /**
     * @param array<string, mixed> $form_row
     */
    private function insertSubmission(int $form_id, array $submission_payload, array $form_row, string $created_utc): int
    {
        global $wpdb;

        $table = $this->config->getTable('submission');
        $compliance_mode = !empty($form_row['compliance_mode']);
        $compliance_profile = sanitize_text_field((string) ($form_row['compliance_profile'] ?? ''));
        $compliance_flags = $this->maybeDecode($form_row['compliance_flags'] ?? '');
        $compliance_flags = is_array($compliance_flags) ? $compliance_flags : [];

        $submission_hash = $compliance_mode ? Compliance::hashSubmission($submission_payload) : '';
        $submission_version = $compliance_mode ? 1 : 0;
        $submission_meta = wp_json_encode([
            'events' => [
                [
                    'type' => 'migration_import',
                    'timestamp' => gmdate('c', strtotime($created_utc)),
                    'page_id' => 0,
                    'source' => 'cf7',
                ],
            ],
            'compliance' => [
                'mode' => $compliance_mode ? 1 : 0,
                'profile' => $compliance_mode ? $compliance_profile : '',
                'flags' => $compliance_mode ? $compliance_flags : [],
                'captured_at' => gmdate('c', strtotime($created_utc)),
            ],
        ]);

        $insert_data = [
            'form_id' => $form_id,
            'status' => 1,
            'is_payment_form' => 0,
            'payment_status' => 'paid',
            'public_token' => $this->generatePublicToken(),
            'submission' => maybe_serialize($submission_payload),
            'form_layout_version' => $compliance_mode ? (int) ($form_row['layout_version'] ?? 0) : 0,
            'form_layout_hash' => $compliance_mode ? (string) ($form_row['layout_hash'] ?? '') : '',
            'layout_snapshot_id' => null,
            'submission_version' => $submission_version,
            'submission_hash' => $submission_hash,
            'submission_ip' => '',
            'submission_user_agent' => '',
            'submission_meta' => $submission_meta,
            'source_url' => '',
            'author' => get_current_user_id(),
            'created' => gmdate('Y-m-d H:i:s', strtotime($created_utc)),
        ];

        $insert = $wpdb->insert(
            $table,
            $insert_data,
            ['%d', '%d', '%d', '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s']
        );

        if ($insert === false) {
            return 0;
        }

        $submission_id = (int) $wpdb->insert_id;
        SubmissionHistoryManager::getInstance()->recordRevision(
            $submission_id,
            $form_id,
            $submission_payload,
            'create',
            [
                'actor_user_id' => get_current_user_id(),
                'actor_label' => (string) wp_get_current_user()->display_name,
                'previous_snapshot' => [],
                'update_pointer' => true,
            ]
        );

        return $submission_id;
    }

    private function incrementLifetimeSubmissionCount(int $form_id, int $count): void
    {
        global $wpdb;

        if ($form_id <= 0 || $count <= 0) {
            return;
        }

        $table = $this->config->getTable('form');
        $wpdb->query(
            $wpdb->prepare(
                "UPDATE {$table} SET lifetime_submission_count = lifetime_submission_count + %d WHERE id = %d",
                $count,
                $form_id
            )
        );
    }

    /**
     * @return mixed
     */
    private function normalizeSubmissionValue(string $field_type, string $value)
    {
        $value = trim($value);

        if ($field_type === 'inputcheckbox') {
            $parts = preg_split('/\s*,\s*/', $value) ?: [];
            return array_values(array_filter(array_map('sanitize_text_field', $parts), static fn($item): bool => $item !== ''));
        }

        if ($field_type === 'termsconditions') {
            return in_array(strtolower($value), ['1', 'yes', 'true', 'accepted'], true) ? '1' : sanitize_text_field($value);
        }

        return sanitize_text_field($value);
    }

    private function normalizeLookupKey(string $value): string
    {
        $value = strtolower(trim(wp_strip_all_tags($value)));
        $value = preg_replace('/\s+/', ' ', $value);
        return sanitize_text_field((string) $value);
    }

    private function generatePublicToken(): string
    {
        try {
            return bin2hex(random_bytes(20));
        } catch (\Throwable $throwable) {
            return wp_generate_password(40, false, false);
        }
    }
}
