<?php
/**
 * Chat REST Controller
 *
 * Handles chat operations including messaging, streaming, search, and research.
 *
 * @package WP_Agentic_Writer
 */

/**
 * Class WP_Agentic_Writer_Controller_Chat
 *
 * REST controller for chat operations.
 *
 * @since 0.3.0
 */
class WP_Agentic_Writer_Controller_Chat
{
    /**
     * Sidebar instance for dependency access.
     *
     * @var WP_Agentic_Writer_Gutenberg_Sidebar
     */
    private $sidebar;

    /**
     * Constructor.
     *
     * @since 0.3.0
     * @param WP_Agentic_Writer_Gutenberg_Sidebar $sidebar Sidebar instance.
     */
    public function __construct($sidebar)
    {
        $this->sidebar = $sidebar;
    }

    /**
     * Handle chat request.
     *
     * @since 0.1.0
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error Response.
     */
    public function handle_chat_request($request)
    {
        // Check rate limit.
        $params = $request->get_json_params();
        $stream = !empty($params["stream"]);
        $endpoint = $stream ? "chat_stream" : "chat";

        $rate_limit = WPAW_Rate_Limiter::check($endpoint);
        if (is_wp_error($rate_limit)) {
            return $rate_limit;
        }

        $messages = $params["messages"] ?? [];
        $post_id = $params["postId"] ?? 0;
        $type = $params["type"] ?? "planning";
        $session_id = $this->sidebar->resolve_or_create_session_id(
            $params["sessionId"] ?? "",
            $post_id,
        );

        // Check post permission if post_id is provided.
        if ($post_id > 0 && !$this->sidebar->check_post_permission($post_id)) {
            return new WP_Error(
                "forbidden",
                __(
                    "You do not have permission to access this post.",
                    "wp-agentic-writer",
                ),
                ["status" => 403],
            );
        }

        $post_config = $this->sidebar->resolve_post_config_from_request(
            $params,
            $post_id,
        );
        $post_config_context = $this->sidebar->build_post_config_context(
            $post_config,
        );

        // Detect language from user's last message for real-time response matching.
        $last_user_message = $this->sidebar->get_last_user_message($messages);
        $detected_from_message = $this->sidebar->detect_language_from_text(
            $last_user_message,
        );
        $stored_language = get_post_meta(
            $post_id,
            "_wpaw_detected_language",
            true,
        );
        $effective_language = $this->sidebar->resolve_language_preference(
            $post_config,
            $detected_from_message ?: $stored_language,
        );

        // Extract focus keyword for context anchoring.
        $focus_keyword = "";
        if (!empty($post_config["focus_keyword"])) {
            $focus_keyword = sanitize_text_field($post_config["focus_keyword"]);
        } elseif (!empty($post_config["seo_focus_keyword"])) {
            $focus_keyword = sanitize_text_field(
                $post_config["seo_focus_keyword"],
            );
        } elseif ($post_id > 0) {
            $focus_keyword = get_post_meta(
                $post_id,
                "_wpaw_focus_keyword",
                true,
            );
        }

        // Build focus keyword instruction for chat.
        $focus_keyword_instruction = "";
        if (!empty($focus_keyword)) {
            $focus_keyword_instruction = "
CONTEXT ANCHOR: The user is working on an article about \"{$focus_keyword}\".
Keep your responses relevant to this primary topic. If the conversation drifts, gently guide it back to \"{$focus_keyword}\".

At the END of your response, if you identify a good focus keyword from the discussion, suggest it in this format:
**Focus Keyword Suggestion:** [your suggested keyword]
";
        }

        $language_instruction = $this->sidebar->build_language_instruction(
            $effective_language,
            "chat responses",
        );
        $system_prompt = "You are a helpful writing assistant. Answer clearly, with concise structure and practical suggestions.
{$focus_keyword_instruction}
CRITICAL LANGUAGE REQUIREMENT:
{$language_instruction}
{$post_config_context}";

        $context_builder = WP_Agentic_Writer_Context_Builder::get_instance();
        $context_package = $context_builder->build_system_message(
            "chat",
            $session_id,
            $post_id,
            array_merge($params, [
                "messages" => $messages,
                "postConfig" => $post_config,
                "latestUserMessage" => $last_user_message,
            ]),
        );

        // OpenRouter is stateless; send only compact saved context plus the latest turn.
        $messages = [];
        if ("" !== trim((string) $last_user_message)) {
            $messages[] = [
                "role" => "user",
                "content" => $last_user_message,
            ];
        }

        $messages = $this->sidebar->prepend_system_prompt(
            $messages,
            $system_prompt,
        );
        if (!empty($context_package["message"])) {
            array_splice($messages, 1, 0, [$context_package["message"]]);
        }

        // Get provider for this task type with selection metadata.
        $provider_result = WP_Agentic_Writer_Provider_Manager::get_provider_for_task(
            $type,
        );
        $provider = $provider_result->provider;
        $provider_warnings = $provider_result->warnings;

        if ($stream) {
            $web_search_options = $this->sidebar->get_web_search_options(
                $post_config,
            );
            $this->stream_chat_request(
                $messages,
                $post_id,
                $type,
                $web_search_options,
                $session_id,
            );
            exit();
        }

        // Send chat request.
        $response = $provider->chat($messages, [], $type);

        if (is_wp_error($response)) {
            return new WP_Error("chat_error", $response->get_error_message(), [
                "status" => 500,
            ]);
        }

        // MEMANTO: Remember user message (fire-and-forget, never blocks).
        do_action(
            "wpaw_memanto_user_message",
            $session_id,
            $last_user_message,
            $post_id,
        );

        // Track cost with provider and session metadata.
        $this->sidebar->track_ai_cost(
            $post_id,
            $response["model"] ?? "",
            "chat",
            $response["input_tokens"] ?? 0,
            $response["output_tokens"] ?? 0,
            $response["cost"] ?? 0,
            $provider_result,
            $session_id,
            "success",
        );

        // Include provider metadata in response (DoD Provider Transparency contract).
        $response["provider"] = $provider_result->actual_provider;
        $response["selected_provider"] = $provider_result->selected_provider;
        $response["fallback_used"] = $provider_result->fallback_used;
        $response["warnings"] = $provider_warnings;
        $response["session_id"] = $session_id;
        $response["context_audit"] = $context_package["audit"] ?? [];
        // Also include nested form for consistency with other AI endpoints.
        $response[
            "provider_metadata"
        ] = $this->sidebar->build_provider_metadata(
            $provider_result,
            $response["model"] ?? "",
        );

        if (!empty($response["content"])) {
            // Storage: Persist to session table via Context Service only.
            // Legacy _wpaw_chat_history post meta is deprecated and no longer written.
            if (!empty($session_id)) {
                $context_service = WP_Agentic_Writer_Context_Service::get_instance();
                $context_service->add_message($session_id, [
                    "role" => "user",
                    "content" => $last_user_message,
                    "timestamp" => current_time("c"),
                ]);
                $context_service->add_message($session_id, [
                    "role" => "assistant",
                    "content" => $response["content"],
                    "timestamp" => current_time("c"),
                ]);
            }
        }

        // MEMANTO: Fire session start on first chat interaction.
        do_action(
            "wpaw_memanto_session_start",
            $session_id,
            $post_id,
            get_current_user_id(),
        );

        return new WP_REST_Response($response, 200);
    }

    /**
     * Stream chat request response.
     *
     * @since 0.1.0
     * @param array  $messages          Chat messages.
     * @param int    $post_id           Post ID.
     * @param string $type              Chat type.
     * @param array  $web_search_options Web search options.
     * @param string $session_id        Session ID for context persistence.
     * @return void
     */
    private function stream_chat_request(
        $messages,
        $post_id,
        $type,
        $web_search_options = [],
        $session_id = "",
    ) {
        header("Content-Type: text/event-stream");
        header("Cache-Control: no-cache");
        header("X-Accel-Buffering: no");

        // Aggressively disable ALL output buffering layers (WordPress nests multiple).
        @ini_set("output_buffering", "Off");
        @ini_set("zlib.output_compression", false);
        while (ob_get_level() > 0) {
            ob_end_flush();
        }
        flush();

        // Initialize streaming state variables.
        $accumulated_content = "";
        $chunks_emitted = 0;
        $total_cost = 0;
        $last_user_message = $this->sidebar->get_last_user_message($messages);

        // MEMANTO: Notify session start.
        do_action(
            "wpaw_memanto_session_start",
            $session_id,
            $post_id,
            get_current_user_id(),
        );

        // Get provider with selection metadata for transparency.
        $provider_result = WP_Agentic_Writer_Provider_Manager::get_provider_for_task(
            $type,
        );
        $provider = $provider_result->provider;
        $provider_warnings = $provider_result->warnings;

        echo "data: " .
            wp_json_encode([
                "type" => "provider",
                "provider" => $provider_result->actual_provider,
                "selectedProvider" => $provider_result->selected_provider,
                "fallback_used" => $provider_result->fallback_used,
                "byok_managed_by" =>
                    "openrouter" === $provider_result->actual_provider
                        ? "openrouter"
                        : "",
            ]) .
            "\n\n";
        flush();

        $this->sidebar->maybe_inject_brave_search(
            $messages,
            $provider,
            $web_search_options,
        );

        $response = $provider->chat_stream(
            $messages,
            $web_search_options,
            $type,
            function ($chunk, $is_complete, $full_content) use (
                &$accumulated_content,
                &$chunks_emitted,
            ) {
                $accumulated_content = $full_content;
                if ("" !== $chunk) {
                    $chunks_emitted++;
                    echo "data: " .
                        wp_json_encode([
                            "type" => "conversational_stream",
                            "content" => $accumulated_content,
                        ]) .
                        "\n\n";
                    if (ob_get_level() > 0) {
                        ob_end_flush();
                    }
                    flush();
                }
            },
        );

        // Fallback: if streaming produced no chunks but we have accumulated content, emit it now.
        if (
            0 === $chunks_emitted &&
            !is_wp_error($response) &&
            !empty($response["content"])
        ) {
            $accumulated_content = $response["content"];
            echo "data: " .
                wp_json_encode([
                    "type" => "conversational_stream",
                    "content" => $accumulated_content,
                ]) .
                "\n\n";
            flush();
        }

        // Failsafe: always use the provider's final returned content if our stream capture failed.
        if (
            empty($accumulated_content) &&
            !is_wp_error($response) &&
            !empty($response["content"])
        ) {
            $accumulated_content = $response["content"];
        }

        error_log(
            "WPAW Stream Debug: Accumulated Content Length: " .
                strlen($accumulated_content) .
                " | Is Error: " .
                (is_wp_error($response) ? "Yes" : "No") .
                " | Session ID: " .
                $session_id,
        );

        if (is_wp_error($response)) {
            echo "data: " .
                wp_json_encode([
                    "type" => "error",
                    "message" => $response->get_error_message(),
                ]) .
                "\n\n";
            flush();
            exit();
        }

        if (empty($accumulated_content)) {
            error_log(
                "WPAW Stream Debug: provider returned empty chat content",
            );
            echo "data: " .
                wp_json_encode([
                    "type" => "error",
                    "message" => __(
                        "The provider returned an empty chat response.",
                        "wp-agentic-writer",
                    ),
                ]) .
                "\n\n";
            flush();
            exit();
        }

        $total_cost = $response["cost"] ?? 0;

        // Debug: Log chat cost tracking (only when WP_DEBUG is on).
        wpaw_debug_log("Tracking chat cost", [
            "post_id" => $post_id,
            "model" => $response["model"] ?? "unknown",
            "type" => $type,
            "cost" => $total_cost,
        ]);

        // Track cost with provider and session metadata.
        $this->sidebar->track_ai_cost(
            $post_id,
            $response["model"] ?? "",
            "chat",
            $response["input_tokens"] ?? 0,
            $response["output_tokens"] ?? 0,
            $total_cost,
            $provider_result,
            $session_id,
            "success",
        );

        if (!empty($accumulated_content)) {
            echo "data: " .
                wp_json_encode([
                    "type" => "conversational",
                    "content" => $accumulated_content,
                ]) .
                "\n\n";
            flush();

            // Storage: Persist to session table via Context Service only.
            // Legacy _wpaw_chat_history post meta is deprecated and no longer written.
            if (!empty($session_id)) {
                $context_service = WP_Agentic_Writer_Context_Service::get_instance();
                $res1 = $context_service->add_message($session_id, [
                    "role" => "user",
                    "content" => $last_user_message,
                    "timestamp" => current_time("c"),
                ]);
                $res2 = $context_service->add_message($session_id, [
                    "role" => "assistant",
                    "content" => $accumulated_content,
                    "timestamp" => current_time("c"),
                ]);
                error_log(
                    "WPAW Stream Debug: add_message result: user=" .
                        ($res1 ? "true" : "false") .
                        ", assistant=" .
                        ($res2 ? "true" : "false"),
                );
            } else {
                error_log(
                    "WPAW Stream Debug: session_id is empty, skipping add_message",
                );
            }

            // MEMANTO: Remember user message from chat.
            do_action(
                "wpaw_memanto_user_message",
                $session_id,
                $last_user_message,
                $post_id,
            );
        }

        // Send provider transparency metadata in completion event.
        echo "data: " .
            wp_json_encode([
                "type" => "complete",
                "totalCost" => $total_cost,
                "session_id" => $session_id,
                "provider" => $provider_result->actual_provider,
                "fallback_used" => $provider_result->fallback_used,
                "warnings" => $provider_warnings,
            ]) .
            "\n\n";
        flush();
    }

    /**
     * Clear chat context for a post.
     *
     * @since 0.1.0
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error Response.
     */
    public function handle_clear_context($request)
    {
        $params = $request->get_json_params();
        $post_id = intval($params["postId"] ?? 0);
        $session_id = sanitize_text_field($params["sessionId"] ?? "");

        if ($post_id <= 0) {
            return new WP_Error(
                "invalid_post",
                __("Invalid post ID.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        // Check post permission before clearing context.
        if (!$this->sidebar->check_post_permission($post_id)) {
            return new WP_Error(
                "forbidden",
                __(
                    "You do not have permission to edit this post.",
                    "wp-agentic-writer",
                ),
                ["status" => 403],
            );
        }

        // Use the context service to clear the session and post meta consistently.
        $context_service = WP_Agentic_Writer_Context_Service::get_instance();
        $context_service->clear_context($session_id, $post_id);

        // MEMANTO: Notify session end on context clear.
        do_action("wpaw_memanto_session_end", $session_id, $post_id);

        return new WP_REST_Response(
            [
                "success" => true,
            ],
            200,
        );
    }

    /**
     * Get chat history for a post (deprecated compatibility endpoint).
     *
     * @since 0.1.0
     * @deprecated 0.2.0 Use /wp-agentic-writer/v1/conversation/{post_id} instead.
     *                   This endpoint reads from conversation sessions via migration.
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error Response.
     */
    public function handle_get_chat_history($request)
    {
        $post_id = intval($request["post_id"] ?? 0);
        if ($post_id <= 0) {
            return new WP_Error(
                "invalid_post",
                __("Invalid post ID.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        if (!$this->sidebar->check_post_permission($post_id)) {
            return new WP_Error(
                "forbidden",
                __(
                    "You do not have permission to access this post.",
                    "wp-agentic-writer",
                ),
                ["status" => 403],
            );
        }

        $history = $this->sidebar->get_post_chat_history($post_id);
        return new WP_REST_Response(
            [
                "messages" => $history,
                "deprecated" => true,
                "message" =>
                    "This endpoint is deprecated. Use conversation sessions instead.",
            ],
            200,
        );
    }

    /**
     * Handle search request.
     *
     * @since 0.1.4
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_search($request)
    {
        $params = $request->get_json_params();
        $query = sanitize_text_field($params["query"] ?? "");
        $count = isset($params["count"]) ? absint($params["count"]) : 5;

        if (empty($query)) {
            return new WP_Error(
                "missing_query",
                __("Search query is required.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        $brave = WP_Agentic_Writer_Brave_Search_API::get_instance();
        $results = $brave->search($query, $count);

        if (is_wp_error($results)) {
            return $results;
        }

        return new WP_REST_Response(
            [
                "query" => $query,
                "results" => $results,
                "count" => count($results),
            ],
            200,
        );
    }

    /**
     * Handle fetch content request for research.
     *
     * Fetches and extracts content from a URL for AI context.
     *
     * @since 0.1.4
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_fetch_content($request)
    {
        $params = $request->get_json_params();
        $url = esc_url_raw($params["url"] ?? "");

        if (empty($url)) {
            return new WP_Error(
                "missing_url",
                __("URL is required.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        // Validate URL format.
        if (!wp_http_validate_url($url)) {
            return new WP_Error(
                "invalid_url",
                __("Invalid URL provided.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        // Fetch the content.
        $response = wp_remote_get($url, [
            "timeout" => 20,
            "user-agent" => "Mozilla/5.0 (compatible; WP-Agentic-Writer/1.0)",
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $http_code = wp_remote_retrieve_response_code($response);
        if (200 !== $http_code) {
            return new WP_Error(
                "fetch_failed",
                sprintf(
                    // translators: %d is the HTTP status code.
                    __(
                        "Undefined error — please try again (HTTP %d).",
                        "wp-agentic-writer",
                    ),
                    $http_code,
                ),
                ["status" => $http_code],
            );
        }

        $body = wp_remote_retrieve_body($response);
        $content = wp_strip_all_tags($body);

        // Truncate to prevent token overflow (max ~4000 chars for context).
        if (strlen($content) > 4000) {
            $content = substr($content, 0, 4000) . "...";
        }

        return new WP_REST_Response(
            [
                "url" => $url,
                "content" => $content,
                "length" => strlen($content),
            ],
            200,
        );
    }

    /**
     * Handle research summary request.
     *
     * Performs multiple searches and generates a research summary.
     *
     * @since 0.1.4
     * @param WP_REST_Request $request REST request.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_research_summary($request)
    {
        $params = $request->get_json_params();
        $topic = sanitize_text_field($params["topic"] ?? "");
        $depth = sanitize_text_field($params["depth"] ?? "basic");
        $include_urls = isset($params["include_urls"])
            ? (bool) $params["include_urls"]
            : false;

        if (empty($topic)) {
            return new WP_Error(
                "missing_topic",
                __("Research topic is required.", "wp-agentic-writer"),
                ["status" => 400],
            );
        }

        // Determine search count based on depth.
        $search_counts = [
            "basic" => 3,
            "medium" => 5,
            "deep" => 8,
        ];
        $count = $search_counts[$depth] ?? 3;

        $brave = WP_Agentic_Writer_Brave_Search_API::get_instance();

        // Perform main search.
        $main_results = $brave->search($topic, $count);

        if (is_wp_error($main_results)) {
            return $main_results;
        }

        $research_data = [
            "topic" => $topic,
            "depth" => $depth,
            "search_results" => $main_results,
            "formatted_context" => $brave->format_results_for_llm(
                $main_results,
                $topic,
            ),
        ];

        // Optionally fetch content from top URLs.
        if ($include_urls && !empty($main_results)) {
            $fetched_content = [];
            $max_urls = min(2, count($main_results)); // Limit to 2 URLs.

            for ($i = 0; $i < $max_urls; $i++) {
                $url = $main_results[$i]["url"] ?? "";
                if (empty($url)) {
                    continue;
                }

                $fetch_response = wp_remote_get($url, [
                    "timeout" => 15,
                    "user-agent" =>
                        "Mozilla/5.0 (compatible; WP-Agentic-Writer/1.0)",
                ]);

                if (
                    !is_wp_error($fetch_response) &&
                    200 === wp_remote_retrieve_response_code($fetch_response)
                ) {
                    $body = wp_remote_retrieve_body($fetch_response);
                    $content = wp_strip_all_tags($body);

                    if (strlen($content) > 2000) {
                        $content = substr($content, 0, 2000) . "...";
                    }

                    $fetched_content[] = [
                        "url" => $url,
                        "content" => $content,
                    ];
                }
            }

            if (!empty($fetched_content)) {
                $research_data["fetched_content"] = $fetched_content;
            }
        }

        return new WP_REST_Response($research_data, 200);
    }
}
