<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

use GriffinForms\Admin\Sql\App;
use GriffinForms\Admin\Sql\Format;
use GriffinForms\Includes\Notifications\NotificationChannelRegistry;

class GenerateNotificationActionPromptHandler extends BasePromptHandler
{
    protected const MAX_REFLECTION_RETRIES = 1;

    protected const KNOWN_CHANNELS   = ['email', 'email_alerts'];
    protected const KNOWN_EVENTS     = ['form_submission', 'payment_received'];
    protected const KNOWN_SUB_EVENTS = [
        'form_submission'   => ['initial_submission', 'submission_edit'],
        'payment_received'  => ['payment_success', 'payment_failed'],
    ];

    public function getPromptKey(): string
    {
        return 'generate_notification_action';
    }

    public function getUnsupportedMessage(): string
    {
        return __('I can only manage email notifications at the form level.', 'griffinforms-form-builder');
    }

    public function handle(int $form_id, string $prompt, string $provider, array $config): array
    {
        $selected_context = is_array($config['selected_context'] ?? null) ? (array) $config['selected_context'] : [];
        $request_context  = is_array($config['request_context'] ?? null) ? (array) $config['request_context'] : [];

        $format_sql = Format::getInstance();
        $form = $format_sql->itemValues($form_id, 'form', ['notifications']);

        $current_notifications = NotificationChannelRegistry::resolveFormNotifications($form);

        // Ensure at least one message exists; create a default if none do.
        $available_messages = $this->getAvailableMessages();
        if (empty($available_messages)) {
            $new_id = $this->createDefaultMessage();
            if ($new_id > 0) {
                $available_messages = $this->getAvailableMessages();
            }
        }

        // Fetch email fields from the form for autoresponder recipient selection.
        $email_fields = $this->getFormEmailFields($form_id);

        // If the request is about autoresponder, no email field exists, and no static address is provided, advise the user.
        if (empty($email_fields) && $this->looksLikeAutoresponderIntent($prompt) && !$this->looksLikeStaticRecipient($prompt)) {
            return $this->formatFailure(
                __('This form has no email field. Add an email field first before setting up an autoresponder.', 'griffinforms-form-builder'),
                'autoresponder_no_email_field',
                409,
                ['request_context' => $request_context]
            );
        }

        $last_errors = [];
        $raw_content = '';
        for ($attempt = 0; $attempt <= self::MAX_REFLECTION_RETRIES; $attempt++) {
            $prompt_text = $attempt === 0
                ? $this->buildUserPrompt($prompt, $form_id, $current_notifications, $available_messages, $email_fields, $selected_context, $request_context)
                : $this->buildReflectionPrompt($prompt, $raw_content, $last_errors);

            $response    = $this->requestStrict(
                $provider,
                $prompt_text,
                ['system' => $this->buildSystemPrompt()],
                array_merge($config, ['timeout' => $attempt === 0 ? 22 : 14])
            );

            $raw_content = (string) ($response['content'] ?? '');
            [$decoded, $extract_error] = $this->extractor->extract($raw_content);

            $validation_errors = [];
            if ($extract_error !== null) {
                $validation_errors[] = $extract_error;
            } elseif (!is_array($decoded)) {
                $validation_errors[] = __('Notification response must decode to an object.', 'griffinforms-form-builder');
            } else {
                $validation_errors = $this->validatePayload($decoded, $available_messages);
            }

            if (empty($validation_errors) && is_array($decoded)) {
                $new_notifications = $this->normalizeNotifications((array) ($decoded['notifications'] ?? []));
                $format_sql->updateItemValue('form', $form_id, 'notifications', wp_json_encode($new_notifications));
                return [
                    'success'       => true,
                    'status'        => 'applied',
                    'message'       => $this->normalizeText((string) ($decoded['success_message'] ?? __('Updated the form notifications.', 'griffinforms-form-builder')), 140),
                    'mutated_ids'   => [$form_id],
                    'prompt_key'    => $this->getPromptKey(),
                    'patch_plan'    => [],
                    'patch_summary' => [],
                ];
            }

            $last_errors = $validation_errors;
            $this->logRetryFailure($this->getPromptKey(), $attempt + 1, $validation_errors, $raw_content);
        }

        return $this->formatFailure(
            __('I could not update the form notifications safely. Try being more specific.', 'griffinforms-form-builder'),
            'notifications_validation_failed',
            422,
            ['request_context' => $request_context]
        );
    }

    protected function buildSystemPrompt(): string
    {
        $channels = implode(', ', self::KNOWN_CHANNELS);
        $events   = implode(', ', self::KNOWN_EVENTS);

        return implode("\n", [
            'You are Griffin Assist managing email notifications for a form in GriffinForms.',
            'Return JSON only. The response MUST have exactly two top-level keys: "notifications" and "success_message".',
            'Exact shape:',
            '{',
            '  "notifications": {',
            '    "form_submission": {',
            '      "initial_submission": [...],',
            '      "submission_edit": [...]',
            '    },',
            '    "payment_received": {',
            '      "payment_success": [...],',
            '      "payment_failed": [...]',
            '    }',
            '  },',
            '  "success_message": "Added an email notification to admin@example.com on new submission."',
            '}',
            'IMPORTANT: "success_message" must be a top-level key — NOT inside "notifications".',
            'IMPORTANT: "success_message" must be a specific, human-readable confirmation of exactly what was changed — e.g. "Added an email notification to admin@example.com for new submissions." or "Disabled the autoresponder." Never use vague responses like "Done." or "Updated."',
            'Each entry in a sub-event array: {"id":null,"order":1,"enabled":true,"channel":"email","message_id":2,"recipients":[]}',
            'Rules:',
            '- Return the full notifications structure — partial structures will lose existing entries.',
            '- Known event IDs: ' . $events . '.',
            '- Sub-events for form_submission: initial_submission, submission_edit.',
            '- Sub-events for payment_received: payment_success, payment_failed.',
            '- Known channels: ' . $channels . '. Do not add channels not in this list.',
            '- id: set to null for new entries; the handler assigns a UUID. Preserve existing IDs for existing entries.',
            '- message_id: must be one of the IDs from the "Available messages" list. Do not invent or guess message IDs.',
            '- recipients: two possible formats: {"type":"field","value":"<field_id>"} for a form email field, or {"type":"static","value":"user@example.com"} for a literal email address.',
            '- STATIC ADDRESS RULE: If the user\'s request contains a literal email address (e.g. "admin@example.com"), you MUST use {"type":"static","value":"<that exact address>"} as the recipient. Do NOT substitute a form field — use the address as written.',
            '- FIELD RECIPIENT RULE: Only use {"type":"field","value":"<field_id>"} when the user asks to send to the form submitter / person who filled in the form AND an "Email fields" list is provided. Choose the most semantically appropriate field ID from that list.',
            '- For admin notifications with no explicit address mentioned: use recipients [].',
            '- Never invent email addresses that were not in the user request.',
            '- AUTORESPONDER RULE: An autoresponder is an entry under form_submission.initial_submission with channel "email". Do NOT create a sub-event key called "email_autoresponder" — that key does not exist.',
            '- IDEMPOTENCY RULE: If form_submission.initial_submission already contains an entry with channel "email" and a recipient of type "field", that entry IS the autoresponder. Update it in place — do NOT add a duplicate. Only add a new entry if no such entry exists.',
            '- Do not return markdown, code fences, or required_inputs.',
        ]);
    }

    protected function buildUserPrompt(string $prompt, int $form_id, array $current_notifications, array $available_messages, array $email_fields, array $selected_context, array $request_context): string
    {
        $parts = [
            'User request:',
            $prompt,
            'Current notifications:',
            wp_json_encode($current_notifications, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Available messages (you MUST use one of these message_id values):',
            wp_json_encode($available_messages, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
        ];

        if (!empty($email_fields)) {
            $parts[] = 'Email fields on this form (use the "id" as the recipient field value ONLY when sending to the form submitter — NOT when the user specified a literal address):';
            $parts[] = wp_json_encode($email_fields, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        // Detect any literal email addresses in the prompt and make them explicit for the model.
        if (preg_match_all('/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/', $prompt, $matches)) {
            $parts[] = 'Literal email address(es) in user request — use these as static recipients (type:"static"):';
            $parts[] = implode(', ', array_unique($matches[0]));
        }

        $parts[] = 'Apply the requested change. Return the full updated notifications structure.';

        return implode("\n", $parts);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors): string
    {
        return implode("\n", [
            'Your previous notifications response was invalid.',
            'Original request:',
            $prompt,
            'Validation errors:',
            wp_json_encode(array_values($errors), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Previous raw response:',
            $raw_content,
            'Return corrected JSON only with a valid notifications object.',
        ]);
    }

    protected function validatePayload(array $decoded, array $available_messages): array
    {
        $errors        = [];
        $notifications = $decoded['notifications'] ?? null;

        if (!is_array($notifications) || empty($notifications)) {
            $errors[] = __('notifications must be a non-empty object.', 'griffinforms-form-builder');
            return $errors;
        }

        $valid_message_ids = array_column($available_messages, 'id');

        foreach ($notifications as $event_id => $sub_events) {
            if (!is_array($sub_events)) {
                continue;
            }
            foreach ($sub_events as $sub_event_id => $entries) {
                if (!is_array($entries)) {
                    continue;
                }
                foreach ($entries as $i => $entry) {
                    if (!is_array($entry)) {
                        $errors[] = sprintf(__('Notification entry %s/%s[%d] is not an object.', 'griffinforms-form-builder'), $event_id, $sub_event_id, $i);
                        continue;
                    }

                    $channel = sanitize_key((string) ($entry['channel'] ?? ''));
                    if (!in_array($channel, self::KNOWN_CHANNELS, true)) {
                        $errors[] = sprintf(
                            __('Notification entry %s/%s[%d] has unsupported channel "%s". Allowed: %s.', 'griffinforms-form-builder'),
                            $event_id, $sub_event_id, $i, $channel, implode(', ', self::KNOWN_CHANNELS)
                        );
                    }

                    $message_id = isset($entry['message_id']) ? absint($entry['message_id']) : 0;
                    if ($message_id > 0 && !empty($valid_message_ids) && !in_array($message_id, $valid_message_ids, true)) {
                        $errors[] = sprintf(
                            __('Notification entry %s/%s[%d] references non-existent message_id %d.', 'griffinforms-form-builder'),
                            $event_id, $sub_event_id, $i, $message_id
                        );
                    }
                }
            }
        }

        return $errors;
    }

    protected function normalizeNotifications(array $notifications): array
    {
        $normalized = [];

        foreach ($notifications as $event_id => $sub_events) {
            $event_id = sanitize_key((string) $event_id);
            // Skip stray scalar values (e.g. success_message misplaced inside notifications by the model)
            if (!is_array($sub_events)) {
                continue;
            }

            $normalized[$event_id] = [];
            foreach ($sub_events as $sub_event_id => $entries) {
                $sub_event_id = sanitize_key((string) $sub_event_id);
                if (!is_array($entries)) {
                    continue;
                }

                $normalized[$event_id][$sub_event_id] = [];
                foreach ($entries as $entry) {
                    if (!is_array($entry)) {
                        continue;
                    }

                    $id = isset($entry['id']) && is_string($entry['id']) && $entry['id'] !== '' && $entry['id'] !== 'null'
                        ? sanitize_text_field($entry['id'])
                        : $this->generateUuid();

                    $normalized[$event_id][$sub_event_id][] = [
                        'id'         => $id,
                        'order'      => absint($entry['order'] ?? 0),
                        'enabled'    => (bool) ($entry['enabled'] ?? true),
                        'channel'    => sanitize_key((string) ($entry['channel'] ?? 'email')),
                        'message_id' => absint($entry['message_id'] ?? 0),
                        'recipients' => $this->normalizeRecipients((array) ($entry['recipients'] ?? [])),
                    ];
                }
            }
        }

        return $normalized;
    }

    protected function normalizeRecipients(array $recipients): array
    {
        $normalized = [];
        foreach ($recipients as $recipient) {
            if (!is_array($recipient)) {
                continue;
            }
            $type  = sanitize_key((string) ($recipient['type'] ?? 'email'));
            $value = sanitize_text_field((string) ($recipient['value'] ?? ''));
            if ($value !== '') {
                $normalized[] = ['type' => $type, 'value' => $value];
            }
        }
        return $normalized;
    }

    protected function looksLikeAutoresponderIntent(string $prompt): bool
    {
        return (bool) preg_match('/\b(autoresponder|auto.responder|auto respond|confirmation email|confirm.*email|email.*confirm|send.*email.*user|email.*submitter|thank.you.*email|welcome.*email)\b/i', $prompt);
    }

    protected function looksLikeStaticRecipient(string $prompt): bool
    {
        // Detect an explicit email address in the prompt (e.g. "to admin@example.com").
        return (bool) preg_match('/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/', $prompt);
    }

    protected function getFormEmailFields(int $form_id): array
    {
        global $wpdb;

        $app_sql   = App::getInstance();
        $field_ids = (array) $app_sql->fieldsInForm($form_id);

        if (empty($field_ids)) {
            return [];
        }

        $field_table  = $wpdb->prefix . 'griffinforms_field';
        $placeholders = implode(',', array_fill(0, count($field_ids), '%d'));

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, name, heading, description FROM {$field_table} WHERE id IN ({$placeholders}) AND field_type = 'inputemail' ORDER BY id ASC",
                ...$field_ids
            ),
            ARRAY_A
        );

        $fields = [];
        foreach ((array) $rows as $row) {
            $fields[] = [
                'id'          => (int) $row['id'],
                'name'        => (string) $row['name'],
                'heading'     => (string) $row['heading'],
                'description' => (string) $row['description'],
            ];
        }

        return $fields;
    }

    protected function getAvailableMessages(): array
    {
        global $wpdb;
        $rows = $wpdb->get_results(
            "SELECT id, name, heading FROM {$wpdb->prefix}griffinforms_message ORDER BY id ASC LIMIT 30"
        );
        $messages = [];
        foreach ((array) $rows as $row) {
            $messages[] = [
                'id'      => (int) $row->id,
                'name'    => (string) $row->name,
                'heading' => (string) $row->heading,
            ];
        }
        return $messages;
    }

    protected function createDefaultMessage(): int
    {
        global $wpdb;
        $table = $wpdb->prefix . 'griffinforms_message';
        $result = $wpdb->insert(
            $table,
            [
                'name'    => __('Confirmation', 'griffinforms-form-builder'),
                'heading' => __('Thank you for your submission', 'griffinforms-form-builder'),
                'content' => __('We have received your submission and will be in touch shortly.', 'griffinforms-form-builder'),
            ],
            ['%s', '%s', '%s']
        );
        return $result !== false ? (int) $wpdb->insert_id : 0;
    }

    protected function generateUuid(): string
    {
        if (function_exists('wp_generate_uuid4')) {
            return wp_generate_uuid4();
        }

        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            random_int(0, 0xffff), random_int(0, 0xffff),
            random_int(0, 0xffff),
            random_int(0x0fff, 0x4fff),
            random_int(0x8000, 0xbfff),
            random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff)
        );
    }
}
