<?php
/**
 * AI Chat conversation orchestration (streaming and non-streaming).
 *
 * Extracted from AIChatController to keep methods small and testable.
 *
 * @package FleekDash\Controllers\AI
 * @since 2.3.0
 */

namespace FleekDash\Controllers\AI;

use FleekDash\AI\Core\AIProviderManager;
use FleekDash\AI\Providers\OpenAI\OpenAIStream;
use FleekDash\AI\Services\DocumentAnalysisService;
use FleekDash\AI\Services\DocumentService;
use FleekDash\AI\Services\WordPressPrompts;
use FleekDash\Core\AuthMiddleware;

defined('ABSPATH') || exit;

/**
 * Orchestrates a single AI chat request after HTTP validation.
 */
final class AIChatConversationService
{
    private const SITE_CONTEXT_BLOCK = "\n\nSITE CONTEXT: ";

    private const MSG_AUTH_CHAT = 'Authentication is required to use AI chat features.';

    private const MSG_MESSAGES_REQUIRED = 'Messages parameter is required and must be a non-empty array';

    /** @var callable(array, int): \WP_REST_Response */
    private $respond;

    private AIChatDocumentPersistence $documentPersistence;

    /**
     * @param callable(array, int=): \WP_REST_Response $respond
     */
    public function __construct(callable $respond)
    {
        $this->respond = $respond;
        $this->documentPersistence = new AIChatDocumentPersistence();
    }

    /**
     * @return \WP_REST_Response|null Null if streaming ended via exit()
     */
    public function run(\WP_REST_Request $request): ?\WP_REST_Response
    {
        return $this->buildChatResponse($request);
    }

    private function buildChatResponse(\WP_REST_Request $request): ?\WP_REST_Response
    {
        $out = null;
        $auth_check = AuthMiddleware::check('authenticated');
        if (!$auth_check['authenticated']) {
            $out = $this->callRespond([
                'success' => false,
                'message' => self::MSG_AUTH_CHAT,
            ], 401);
        } else {
            $json_params = $request->get_json_params();
            $body_params = $request->get_body_params();
            $query_params = $request->get_params();

            $messages = $this->resolveMessagesFromRequestSources($json_params, $body_params, $query_params);
            $validation = $this->validateMessagesParameter($messages);
            if ($validation !== null) {
                $out = $validation;
            } else {
                /** @var array<int, array<string, mixed>> $messages */
                $out = $this->runChatWithValidatedMessages($json_params, $body_params, $query_params, $messages);
            }
        }

        return $out;
    }

    /**
     * @param mixed $json_params
     * @param mixed $body_params
     * @param mixed $query_params
     * @param array<int, array<string, mixed>> $messages
     */
    private function runChatWithValidatedMessages($json_params, $body_params, $query_params, array $messages): ?\WP_REST_Response
    {
        $providerGate = $this->getActiveProviderReadyOrErrorResponse();
        $out = $providerGate['error'];
        if ($out === null) {
            $provider = $providerGate['provider'];
            $msgValidation = $this->validateMessageEntries($messages);
            if ($msgValidation !== null) {
                $out = $msgValidation;
            } else {
                $stream = $json_params['stream'] ?? $body_params['stream'] ?? $query_params['stream'] ?? true;

                $options = [
                    'model' => $json_params['model'] ?? $body_params['model'] ?? $query_params['model'] ?? null,
                    'temperature' => $json_params['temperature'] ?? $body_params['temperature'] ?? $query_params['temperature'] ?? null,
                    'max_tokens' => $json_params['max_tokens'] ?? $body_params['max_tokens'] ?? $query_params['max_tokens'] ?? null,
                ];
                $options = array_filter($options, static function ($value) {
                    return $value !== null;
                });

                $context_type = $json_params['context_type'] ?? $body_params['context_type'] ?? $query_params['context_type'] ?? '';
                $is_comment_reply = ($context_type === 'comment_reply');

                $document_result = null;
                if (!$is_comment_reply) {
                    $document_result = $this->applyNonCommentDocumentFlow($messages);
                } else {
                    $this->addSystemMessageIfNeeded($messages, '', true);
                }

                $out = $this->executeChatTransport($provider, $messages, $options, (bool) $stream, $document_result);
            }
        }

        return $out;
    }

    /**
     * @return array{error: ?\WP_REST_Response, provider: object|null}
     */
    private function getActiveProviderReadyOrErrorResponse(): array
    {
        $manager = AIProviderManager::getInstance();
        $provider = $manager->getActiveProvider();
        $error = null;
        if (!$provider) {
            $error = $this->callRespond([
                'success' => false,
                'message' => 'No AI provider is configured. Please configure a provider in settings.',
            ], 400);
        } elseif (!$provider->isConfigured()) {
            $error = $this->callRespond([
                'success' => false,
                'message' => sprintf(
                    '%s API key is not configured. Please configure it in settings.',
                    $provider->getName()
                ),
            ], 400);
        }

        return [
            'error' => $error,
            'provider' => $error === null ? $provider : null,
        ];
    }

    /**
     * @param mixed $json_params
     * @param mixed $body_params
     * @param mixed $query_params
     */
    private function resolveMessagesFromRequestSources($json_params, $body_params, $query_params): ?array
    {
        $sources = [$json_params, $body_params, $query_params];
        foreach ($sources as $src) {
            if (isset($src) && \is_array($src) && \array_key_exists('messages', $src)) {
                return $src['messages'];
            }
        }

        return null;
    }

    /**
     * @param mixed $messages
     */
    private function validateMessagesParameter($messages): ?\WP_REST_Response
    {
        $out = null;
        if ($messages === null) {
            error_log('[AIChatController] messages param is null - returning 400');
            $out = $this->callRespond([
                'success' => false,
                'message' => self::MSG_MESSAGES_REQUIRED,
            ], 400);
        } elseif (!\is_array($messages)) {
            error_log('[AIChatController] messages param is not array - returning 400');
            $out = $this->callRespond([
                'success' => false,
                'message' => 'Messages parameter must be an array',
            ], 400);
        } elseif (empty($messages)) {
            error_log('[AIChatController] messages param empty array - returning 400');
            $out = $this->callRespond([
                'success' => false,
                'message' => self::MSG_MESSAGES_REQUIRED,
            ], 400);
        }

        return $out;
    }

    /**
     * @param array<int, mixed> $messages
     */
    private function validateMessageEntries(array $messages): ?\WP_REST_Response
    {
        $out = null;
        foreach ($messages as $index => $message) {
            if (!\is_array($message) || empty($message['role']) || empty($message['content'])) {
                $out = $this->callRespond([
                    'success' => false,
                    'message' => sprintf(
                        'Message at index %d is invalid. Each message must have "role" and "content" fields.',
                        $index
                    ),
                ], 400);
                break;
            }
        }

        return $out;
    }

    /**
     * @param array<int, array<string, mixed>> $messages
     * @return array<string, mixed>|null Document result from createDocument, or null
     */
    private function applyNonCommentDocumentFlow(array &$messages): ?array
    {
        $out = null;
        $last_user_message = $this->findLastUserMessageContent($messages);
        if ($last_user_message === '') {
            $site_context = DocumentAnalysisService::getSiteContext();
            $this->addSystemMessageIfNeeded($messages, $site_context, false);
        } else {
            $document_analysis = DocumentAnalysisService::analyzeDocumentRequest($last_user_message);
            if ($document_analysis['should_create']) {
                $out = $this->handleDocumentCreationBranch($messages, $last_user_message, $document_analysis);
            } elseif ($document_analysis['needs_clarification']) {
                $this->pushClarificationSystemMessage($messages);
            } else {
                $site_context = DocumentAnalysisService::getSiteContext();
                $this->addSystemMessageIfNeeded($messages, $site_context, false);
            }
        }

        return $out;
    }

    /**
     * @param array<int, array<string, mixed>> $messages
     */
    private function pushClarificationSystemMessage(array &$messages): void
    {
        $site_context = DocumentAnalysisService::getSiteContext();
        array_unshift($messages, [
            'role' => 'system',
            'content' => WordPressPrompts::getSystemPrompt() .
                self::SITE_CONTEXT_BLOCK . $site_context .
                "\n\nCLARIFICATION NEEDED: The user's request might be asking to create a document, but it's not clear. " .
                "Ask them to clarify if they want to create a document about this topic, or if they just want information. " .
                "Example: 'Would you like me to create a document about this topic, or do you just want information?'",
        ]);
    }

    /**
     * @param array<int, array<string, mixed>> $messages
     */
    private function findLastUserMessageContent(array $messages): string
    {
        foreach (array_reverse($messages) as $message) {
            if (isset($message['role']) && $message['role'] === 'user') {
                return $message['content'] ?? '';
            }
        }

        return '';
    }

    /**
     * @param array<int, array<string, mixed>> $messages
     * @param array<string, mixed> $document_analysis
     * @return array<string, mixed>|null
     */
    private function handleDocumentCreationBranch(
        array &$messages,
        string $last_user_message,
        array $document_analysis,
    ): ?array {
        $target_post_type = DocumentAnalysisService::detectTargetPostType($last_user_message);

        $document_result = DocumentService::createDocument([
            'title' => $document_analysis['title'],
            'kind' => 'text',
            'post_type' => $target_post_type,
        ]);

        if ($document_result && isset($document_result['success']) && $document_result['success']) {
            if (!empty($document_result['document_id'])) {
                $postId = isset($document_result['post_id']) ? (int) $document_result['post_id'] : null;
                OpenAIStream::setPendingDocumentMetaId($document_result['document_id'], $postId);
            }

            $site_context = DocumentAnalysisService::getSiteContext();
            $post_type_label = DocumentAnalysisService::getPostTypeLabel($target_post_type);

            array_unshift($messages, [
                'role' => 'system',
                'content' => WordPressPrompts::getSystemPrompt() .
                    self::SITE_CONTEXT_BLOCK . $site_context .
                    "\n\n{$post_type_label} CREATED: A {$post_type_label} titled '{$document_analysis['title']}' has been created in WordPress. " .
                    "Provide a brief conversational response acknowledging the {$post_type_label} creation, then provide comprehensive content about this topic. " .
                    "Format your response as: [CONVERSATION] brief response [/CONVERSATION] [DOCUMENT] comprehensive content [/DOCUMENT]",
            ]);
        } else {
            $site_context = DocumentAnalysisService::getSiteContext();
            $this->addSystemMessageIfNeeded($messages, $site_context, false);
        }

        return $document_result;
    }

    /**
     * @param \FleekDash\AI\Core\AIProviderInterface $provider
     * @param array<int, array<string, mixed>> $messages
     * @param array<string, mixed> $options
     * @param array<string, mixed>|null $document_result
     */
    private function executeChatTransport(
        $provider,
        array $messages,
        array $options,
        bool $stream,
        ?array $document_result,
    ): ?\WP_REST_Response {
        $out = null;
        if ($stream) {
            $out = $this->runStreamingChat($provider, $messages, $options);
        } else {
            $out = $this->runNonStreamingChat($provider, $messages, $options, $document_result);
        }

        return $out;
    }

    /**
     * @param \FleekDash\AI\Core\AIProviderInterface $provider
     * @param array<int, array<string, mixed>> $messages
     * @param array<string, mixed> $options
     */
    private function runStreamingChat($provider, array $messages, array $options): ?\WP_REST_Response
    {
        if (class_exists('\FleekDash\AI\Providers\OpenAI\OpenAIStream')) {
            \FleekDash\AI\Providers\OpenAI\OpenAIStream::setupStreamingHeaders();
        }

        try {
            $provider->streamChat($messages, static function (): void {
                // Per-chunk handling not required; OpenAIStream writes SSE output directly.
            }, $options);
        } catch (\Exception $e) {
            return $this->callRespond([
                'success' => false,
                'message' => $e->getMessage(),
            ], 400);
        }

        /*
         * WordPress REST always JSON-encodes the callback return value unless
         * rest_pre_serve_request returns true. Raw SSE was already echoed during streamChat.
         */
        $done = new \WP_REST_Response(null, 200);
        $done->set_data([
            '_fleekdash_sse_stream_complete' => true,
        ]);

        return $done;
    }

    /**
     * @param \FleekDash\AI\Core\AIProviderInterface $provider
     * @param array<int, array<string, mixed>> $messages
     * @param array<string, mixed> $options
     * @param array<string, mixed>|null $document_result
     */
    private function runNonStreamingChat(
        $provider,
        array $messages,
        array $options,
        ?array $document_result,
    ): \WP_REST_Response {
        try {
            $response = $provider->chat($messages, $options);
            $content = $response['content'] ?? '';

            if ($document_result && isset($document_result['success']) && $document_result['success'] && !empty($document_result['document_id'])) {
                $this->documentPersistence->persistIfDocumentSectionPresent($document_result['document_id'], $content);
            }

            return \rest_ensure_response([
                'success' => true,
                'response' => $content,
                'model' => $response['model'] ?? null,
                'usage' => $response['usage'] ?? [],
            ]);
        } catch (\Exception $e) {
            return $this->callRespond([
                'success' => false,
                'message' => $e->getMessage(),
            ], 400);
        }
    }

    /**
     * @param array<int, array<string, mixed>> $messages
     */
    private function addSystemMessageIfNeeded(array &$messages, string $site_context = '', bool $is_comment_reply = false): void
    {
        $has_system_message = false;
        foreach ($messages as $message) {
            if (isset($message['role']) && $message['role'] === 'system') {
                $has_system_message = true;
                break;
            }
        }

        if (!$has_system_message) {
            if ($is_comment_reply) {
                array_unshift($messages, [
                    'role' => 'system',
                    'content' => 'You are a helpful assistant that writes replies to user comments for a WordPress admin. Always be clear, concise, and professional. Return ONLY the reply text - do NOT use [CONVERSATION] or [DOCUMENT] tags or any formatting markers.',
                ]);
            } else {
                $content = WordPressPrompts::getSystemPrompt();
                if ($site_context !== '') {
                    $content .= self::SITE_CONTEXT_BLOCK . $site_context;
                }
                array_unshift($messages, [
                    'role' => 'system',
                    'content' => $content,
                ]);
            }
        }
    }

    private function callRespond(array $data, int $status = 200): \WP_REST_Response
    {
        $fn = $this->respond;

        return $fn($data, $status);
    }
}
