<?php

namespace GriffinForms\Includes\Ai\PromptRegistry\Handlers;

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

class GenerateMessageMutationPromptHandler extends BasePromptHandler
{
    protected const MAX_REFLECTION_RETRIES = 1;

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

    public function getUnsupportedMessage(): string
    {
        return __('I can only modify email message content (subject, heading, body).', '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();

        $message_id = $this->resolveMessageId($form_id, $prompt, $selected_context, $request_context, $format_sql);

        // Ambiguous: multiple messages; return advisory
        if ($message_id === -1) {
            return $this->buildAmbiguousAdvisory($form_id, $prompt, $request_context, $format_sql);
        }

        if ($message_id <= 0) {
            return $this->formatFailure(
                __('I could not identify which message to update. Select a notification or specify which message to edit.', 'griffinforms-form-builder'),
                'message_not_found',
                409,
                ['request_context' => $request_context]
            );
        }

        $message = $format_sql->itemValues($message_id, 'message', ['name', 'heading', 'description', 'content']);
        if (!$message) {
            return $this->formatFailure(
                __('The message could not be found.', 'griffinforms-form-builder'),
                'message_not_found',
                404,
                ['request_context' => $request_context]
            );
        }

        $current = [
            'name'        => (string) ($message->name ?? ''),
            'heading'     => (string) ($message->heading ?? ''),
            'description' => (string) ($message->description ?? ''),
            'content'     => (string) ($message->content ?? ''),
        ];

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

            $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[] = __('Message mutation response must decode to an object.', 'griffinforms-form-builder');
            } else {
                $validation_errors = $this->validatePayload($decoded, $current);
            }

            if (empty($validation_errors) && is_array($decoded)) {
                $new_message = $this->normalizeMessage((array) ($decoded['message'] ?? []), $current);
                $this->writeMessage($message_id, $new_message, $format_sql);
                return [
                    'success'       => true,
                    'status'        => 'applied',
                    'message'       => $this->normalizeText((string) ($decoded['success_message'] ?? __('Updated the message.', 'griffinforms-form-builder')), 140),
                    'mutated_ids'   => [$message_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 message safely. Try being more specific about what to change.', 'griffinforms-form-builder'),
            'message_mutation_validation_failed',
            422,
            ['request_context' => $request_context]
        );
    }

    // -------------------------------------------------------------------------
    // Message ID resolution
    // -------------------------------------------------------------------------

    protected function resolveMessageId(int $form_id, string $prompt, array $selected_context, array $request_context, Format $format_sql): int
    {
        // Path 0: pre-resolved choice — user selected a message from an advisory list
        $suggestion_context = is_array($request_context['suggestion_context'] ?? null) ? (array) $request_context['suggestion_context'] : [];
        $suggestion_meta_raw = $suggestion_context['suggestion_meta'] ?? [];
        $suggestion_meta = is_array($suggestion_meta_raw) ? $suggestion_meta_raw : (is_string($suggestion_meta_raw) ? (json_decode($suggestion_meta_raw, true) ?: []) : []);
        $resolved_message_id = absint($suggestion_meta['message_id'] ?? 0);
        if ($resolved_message_id > 0) {
            return $resolved_message_id;
        }

        // Path 1: notification item_type selected
        if (sanitize_key((string) ($selected_context['item_type'] ?? '')) === 'notification') {
            $notification_id = sanitize_text_field((string) ($selected_context['item_id'] ?? ''));
            $mid = $this->resolveMessageIdFromNotificationId($form_id, $notification_id, $format_sql);
            if ($mid > 0) {
                return $mid;
            }
        }

        // Path 2: prompt implies a known notification type
        $form = $format_sql->itemValues($form_id, 'form', ['notifications', 'autoresponder', 'alert_emails']);
        $notifications = NotificationChannelRegistry::resolveFormNotifications($form);

        $all_message_ids = $this->collectAllMessageIds($notifications);

        if (empty($all_message_ids)) {
            return 0;
        }

        // If only one message exists, use it
        $unique_ids = array_unique($all_message_ids);
        if (count($unique_ids) === 1) {
            return (int) reset($unique_ids);
        }

        // Prompt heuristic: infer target notification by event/sub-event and recipient type.
        $normalized = strtolower($prompt);

        // "autoresponder" / "confirmation" / "submitter" → form_submission.initial_submission with a field recipient
        if (preg_match('/\b(autoresponder|auto.responder|confirmation|respondent|submitter|person who submitted)\b/', $normalized)) {
            $mid = $this->findMessageIdBySubEventAndRecipientType($notifications, 'form_submission', 'initial_submission', 'field');
            if ($mid > 0) {
                return $mid;
            }
        }

        // "alert" / "admin" / "staff" / "team" → form_submission.initial_submission with a static recipient
        if (preg_match('/\b(alert|admin|staff|team|internal|notification)\b/', $normalized)) {
            $mid = $this->findMessageIdBySubEventAndRecipientType($notifications, 'form_submission', 'initial_submission', 'static');
            if ($mid > 0) {
                return $mid;
            }
        }

        // "submission edit" / "edit notification" → form_submission.submission_edit
        if (preg_match('/\b(submission\s+edit|edit\s+notification|edited\s+submission)\b/', $normalized)) {
            $mid = $this->findMessageIdBySubEvent($notifications, 'form_submission', 'submission_edit');
            if ($mid > 0) {
                return $mid;
            }
        }

        // Multiple messages — ambiguous
        return -1;
    }

    protected function resolveMessageIdFromNotificationId(int $form_id, string $notification_uuid, Format $format_sql): int
    {
        if ($notification_uuid === '') {
            return 0;
        }

        $form = $format_sql->itemValues($form_id, 'form', ['notifications', 'autoresponder', 'alert_emails']);
        $notifications = NotificationChannelRegistry::resolveFormNotifications($form);

        foreach ($notifications as $event_entries) {
            if (!is_array($event_entries)) {
                continue;
            }
            foreach ($event_entries as $entries) {
                if (!is_array($entries)) {
                    continue;
                }
                foreach ($entries as $entry) {
                    if (is_array($entry) && (string) ($entry['id'] ?? '') === $notification_uuid) {
                        return absint($entry['message_id'] ?? 0);
                    }
                }
            }
        }

        return 0;
    }

    protected function collectAllMessageIds(array $notifications): array
    {
        $ids = [];
        foreach ($notifications as $event_entries) {
            if (!is_array($event_entries)) {
                continue;
            }
            foreach ($event_entries as $entries) {
                if (!is_array($entries)) {
                    continue;
                }
                foreach ($entries as $entry) {
                    $mid = absint($entry['message_id'] ?? 0);
                    if ($mid > 0) {
                        $ids[] = $mid;
                    }
                }
            }
        }
        return $ids;
    }

    protected function findMessageIdBySubEvent(array $notifications, string $event_id, string $sub_event_id): int
    {
        $entries = $notifications[$event_id][$sub_event_id] ?? [];
        if (!is_array($entries)) {
            return 0;
        }
        foreach ($entries as $entry) {
            $mid = absint($entry['message_id'] ?? 0);
            if ($mid > 0) {
                return $mid;
            }
        }
        return 0;
    }

    protected function findMessageIdBySubEventAndRecipientType(array $notifications, string $event_id, string $sub_event_id, string $recipient_type): int
    {
        $entries = $notifications[$event_id][$sub_event_id] ?? [];
        if (!is_array($entries)) {
            return 0;
        }
        foreach ($entries as $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $recipients = is_array($entry['recipients'] ?? null) ? $entry['recipients'] : [];
            foreach ($recipients as $recipient) {
                if (sanitize_key((string) ($recipient['type'] ?? '')) === $recipient_type) {
                    $mid = absint($entry['message_id'] ?? 0);
                    if ($mid > 0) {
                        return $mid;
                    }
                }
            }
        }
        return 0;
    }

    protected function buildAmbiguousAdvisory(int $form_id, string $prompt, array $request_context, Format $format_sql): array
    {
        $form = $format_sql->itemValues($form_id, 'form', ['notifications', 'autoresponder', 'alert_emails']);
        $notifications = NotificationChannelRegistry::resolveFormNotifications($form);

        // Collect unique message_ids across all notifications.
        $seen = [];
        $message_ids = [];
        foreach ($notifications as $event_entries) {
            if (!is_array($event_entries)) {
                continue;
            }
            foreach ($event_entries as $entries) {
                if (!is_array($entries)) {
                    continue;
                }
                foreach ($entries as $entry) {
                    $mid = absint($entry['message_id'] ?? 0);
                    if ($mid > 0 && !isset($seen[$mid])) {
                        $seen[$mid] = true;
                        $message_ids[] = $mid;
                    }
                }
            }
        }

        // Fetch message names for human-readable choices.
        $choices = [];
        if (!empty($message_ids)) {
            global $wpdb;
            $placeholders = implode(',', array_fill(0, count($message_ids), '%d'));
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT id, name FROM {$wpdb->prefix}griffinforms_message WHERE id IN ({$placeholders}) ORDER BY id ASC",
                    ...$message_ids
                ),
                ARRAY_A
            );
            foreach ((array) $rows as $row) {
                $name = sanitize_text_field((string) ($row['name'] ?? ''));
                if ($name === '') {
                    $name = 'Message #' . absint($row['id']);
                }
                $choices[] = [
                    'label'           => $name,
                    'suggested_prompt' => $prompt,
                    'suggestion_meta' => wp_json_encode(['message_id' => absint($row['id'])]),
                ];
            }
        }

        return [
            'success'    => true,
            'status'     => 'advisory',
            'message'    => __('This form has multiple messages. Which one would you like to edit?', 'griffinforms-form-builder'),
            'mutated_ids' => [],
            'prompt_key' => $this->getPromptKey(),
            'advisory'   => [
                'response_type' => 'text_with_choices',
                'message'       => __('This form has multiple messages. Which one would you like to edit?', 'griffinforms-form-builder'),
                'choices'       => $choices,
            ],
        ];
    }

    // -------------------------------------------------------------------------
    // Prompt building
    // -------------------------------------------------------------------------

    protected function buildSystemPrompt(): string
    {
        return implode("\n", [
            'You are Griffin Assist updating the content of an email message in GriffinForms.',
            'Return JSON only.',
            'Exact shape: {"message":{"name":"string","heading":"string","description":"string","content":"string"},"success_message":"string"}',
            'Rules:',
            '- All four keys (name, heading, description, content) must be present.',
            '- heading is the email subject line. When the user says "subject line", "subject", or "email subject", they mean heading.',
            '- name is the internal message label shown in admin dropdowns — NOT the subject. Only change name if the user explicitly asks to rename the message or change its internal name.',
            '- description is an internal note about the message. Preserve it unchanged unless the user explicitly asks to change the description.',
            '- content is the message body.',
            '- PRESERVE RULE: If the user only asks to change one field (e.g. subject/heading), return all other fields exactly as they appear in "Current message". Do NOT blank or alter fields the user did not mention.',
            '- Preserve existing merge tokens exactly: {form:...}, {field:ID.attr}, {submission:...}. Do not remove or modify tokens not mentioned in the prompt.',
            '- success_message must be a specific confirmation, e.g. "Updated the subject line to \'Thank you for your submission\'." or "Rewrote the message body." Never use vague responses like "Done." or "Updated."',
            '- Do not return markdown, code fences, or required_inputs.',
        ]);
    }

    protected function buildUserPrompt(string $prompt, int $message_id, array $current, array $selected_context, array $request_context): string
    {
        return implode("\n", [
            'User request:',
            $prompt,
            'Current message (id: ' . $message_id . '):',
            wp_json_encode($current, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Apply the requested change. Return the full updated message object with all four keys.',
            'Preserve any merge tokens ({form:...}, {field:...}, {submission:...}) that are not mentioned in the request.',
        ]);
    }

    protected function buildReflectionPrompt(string $prompt, string $raw_content, array $errors, array $current): string
    {
        return implode("\n", [
            'Your previous message 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,
            'Current message for reference:',
            wp_json_encode($current, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            'Return corrected JSON only with a valid message object containing name, heading, description, and content.',
        ]);
    }

    // -------------------------------------------------------------------------
    // Validation and normalization
    // -------------------------------------------------------------------------

    protected function validatePayload(array $decoded, array $current): array
    {
        $message = $decoded['message'] ?? null;
        if (!is_array($message)) {
            return [__('message must be an object with name, heading, description, and content.', 'griffinforms-form-builder')];
        }

        $errors = [];
        foreach (['name', 'heading', 'description', 'content'] as $key) {
            if (!array_key_exists($key, $message)) {
                $errors[] = sprintf(__('message.%s is missing.', 'griffinforms-form-builder'), $key);
            }
        }

        if (empty($errors)) {
            $name = trim((string) ($message['name'] ?? ''));
            if ($name === '') {
                $errors[] = __('message.name (internal message label) must not be empty.', 'griffinforms-form-builder');
            }
        }

        return $errors;
    }

    protected function normalizeMessage(array $message, array $current): array
    {
        $heading     = $this->normalizeText((string) ($message['heading'] ?? ''), 255);
        $description = $this->normalizeText((string) ($message['description'] ?? ''), 500);

        // Guard: if the model blanked a field that had content, keep the original.
        if ($heading === '' && $current['heading'] !== '') {
            $heading = $current['heading'];
        }
        if ($description === '' && $current['description'] !== '') {
            $description = $current['description'];
        }

        return [
            'name'        => $this->normalizeText((string) ($message['name'] ?? $current['name']), 255),
            'heading'     => $heading,
            'description' => $description,
            'content'     => (string) ($message['content'] ?? $current['content']),
        ];
    }

    protected function writeMessage(int $message_id, array $message, Format $format_sql): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'griffinforms_message';
        $wpdb->update(
            $table,
            [
                'name'        => $message['name'],
                'heading'     => $message['heading'],
                'description' => $message['description'],
                'content'     => $message['content'],
            ],
            ['id' => $message_id],
            ['%s', '%s', '%s', '%s'],
            ['%d']
        );
    }
}
