<?php
/**
 * Context Service
 *
 * Centralized service for managing conversation context across
 * all generation paths. Provides a single source of truth for
 * chat history, plan, and per-post configuration.
 *
 * @package WP_Agentic_Writer
 */

if (!defined("ABSPATH")) {
    exit();
}

/**
 * Class WP_Agentic_Writer_Context_Service
 *
 * Single source of truth for conversation context.
 *
 * Storage Layer Rules:
 * - Conversation messages → wpaw_conversations table (authoritative)
 * - Article outline/plan → post_meta._wpaw_plan (authoritative)
 * - Per-post config → post_meta._wpaw_post_config (authoritative)
 * - Legacy _wpaw_chat_history → migrated to session table on read
 */
class WP_Agentic_Writer_Context_Service
{
    /**
     * Singleton instance.
     *
     * @var WP_Agentic_Writer_Context_Service
     */
    private static $instance = null;

    /**
     * Get singleton instance.
     *
     * @return WP_Agentic_Writer_Context_Service
     */
    public static function get_instance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     */
    private function __construct()
    {
        // Private constructor for singleton.
    }

    /**
     * Get conversation context for a session.
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID (optional).
     * @return array Context data.
     */
    public function get_context($session_id, $post_id = 0)
    {
        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        $session = $manager->get_session($session_id);
        $effective_session_id = $session_id;

        // Migrate legacy history on read if no session exists but post has legacy data.
        if (!$session && $post_id > 0) {
            $legacy_history = get_post_meta(
                $post_id,
                "_wpaw_chat_history",
                true,
            );
            $is_migrated = get_post_meta(
                $post_id,
                "_wpaw_chat_history_migrated",
                true,
            );

            if (!empty($legacy_history) && empty($is_migrated)) {
                $migrated_session_id = $this->migrate_legacy_chat_history(
                    $post_id,
                    $session_id,
                );
                // Use the session_id returned from migration (may be newly created)
                $effective_session_id = is_string($migrated_session_id)
                    ? $migrated_session_id
                    : $session_id;
                $session = $manager->get_session($effective_session_id);
            }
        }

        if (!$session) {
            return $this->get_empty_context($effective_session_id, $post_id);
        }

        // If post_id is provided, get post-specific data
        $post_data = [];
        if ($post_id > 0) {
            $post_data = $this->get_post_context($post_id);
        }

        return [
            "session_id" => $effective_session_id,
            "post_id" => $post_id,
            "messages" => $session["messages"] ?? [],
            "context" => $session["context"] ?? [],
            "plan" => $post_data["plan"] ?? null,
            "post_config" =>
                $post_data["post_config"] ?? $this->get_default_post_config(),
            "title" => $session["title"] ?? "",
            "focus_keyword" => $session["focus_keyword"] ?? "",
            "status" => $session["status"] ?? "active",
        ];
    }

    /**
     * Get empty context structure.
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID.
     * @return array Empty context.
     */
    private function get_empty_context($session_id, $post_id)
    {
        return [
            "session_id" => $session_id,
            "post_id" => $post_id,
            "messages" => [],
            "context" => [],
            "plan" => null,
            "post_config" => $this->get_default_post_config(),
            "title" => "",
            "focus_keyword" => "",
            "status" => "active",
        ];
    }

    /**
     * Get post-specific context (plan, config).
     *
     * @param int $post_id Post ID.
     * @return array Post context.
     */
    public function get_post_context($post_id)
    {
        $plan = get_post_meta($post_id, "_wpaw_plan", true);
        if (!is_array($plan)) {
            $plan = null;
        }

        $post_config = get_post_meta($post_id, "_wpaw_post_config", true);
        if (!is_array($post_config)) {
            $post_config = $this->get_default_post_config();
        }

        return [
            "plan" => $plan,
            "post_config" => $post_config,
        ];
    }

    /**
     * Save messages to session.
     *
     * @param string $session_id Session ID.
     * @param array  $messages   Messages array.
     * @return bool Success.
     */
    public function save_messages($session_id, $messages)
    {
        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        return $manager->update_messages($session_id, $messages);
    }

    /**
     * Add a message to the session.
     *
     * @param string $session_id Session ID.
     * @param array  $message   Message data.
     * @return bool Success.
     */
    public function add_message($session_id, $message)
    {
        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        $session = $manager->get_session($session_id);

        if (!$session) {
            error_log(
                "WPAW_Context_Service: Failed to get session $session_id for add_message",
            );
            return false;
        }

        $messages = $session["messages"] ?? [];
        $messages[] = $message;

        $result = $manager->update_messages($session_id, $messages);
        if (!$result) {
            error_log(
                "WPAW_Context_Service: Failed to update_messages for session $session_id",
            );
        }
        return $result;
    }

    /**
     * Get structured session context JSON.
     *
     * @since 0.2.3
     * @param string $session_id Session ID.
     * @return array Session context.
     */
    public function get_session_context($session_id)
    {
        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        $session = $manager->get_session($session_id);

        if (
            !$session ||
            empty($session["context"]) ||
            !is_array($session["context"])
        ) {
            return [];
        }

        return $session["context"];
    }

    /**
     * Merge a context patch into the stored session context.
     *
     * @since 0.2.3
     * @param string $session_id Session ID.
     * @param array  $patch      Context fields to merge.
     * @return bool Success.
     */
    public function update_session_context($session_id, $patch)
    {
        if (empty($session_id) || !is_array($patch)) {
            return false;
        }

        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        $context = $this->get_session_context($session_id);
        $context = $this->merge_context_recursive($context, $patch);
        $context["updated_at"] = current_time("c");

        return $manager->update_context($session_id, $context);
    }

    /**
     * Append an item to an array inside session context.
     *
     * @since 0.2.3
     * @param string $session_id Session ID.
     * @param string $key        Context key.
     * @param array  $item       Item to append.
     * @param int    $limit      Maximum retained items.
     * @return bool Success.
     */
    public function append_session_context_item(
        $session_id,
        $key,
        $item,
        $limit = 25,
    ) {
        if (empty($session_id) || empty($key) || !is_array($item)) {
            return false;
        }

        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
        $context = $this->get_session_context($session_id);
        if (empty($context[$key]) || !is_array($context[$key])) {
            $context[$key] = [];
        }

        $item["created_at"] = $item["created_at"] ?? current_time("c");
        $context[$key][] = $item;

        if ($limit > 0 && count($context[$key]) > $limit) {
            $context[$key] = array_slice($context[$key], -1 * $limit);
        }

        $context["updated_at"] = current_time("c");

        return $manager->update_context($session_id, $context);
    }

    /**
     * Save plan to post meta.
     *
     * @param int   $post_id Post ID.
     * @param array $plan    Plan data.
     * @return bool Success.
     */
    public function save_plan($post_id, $plan)
    {
        if ($post_id <= 0) {
            return false;
        }

        return update_post_meta($post_id, "_wpaw_plan", $plan) !== false;
    }

    /**
     * Get plan from post meta.
     *
     * @param int $post_id Post ID.
     * @return array|null Plan or null.
     */
    public function get_plan($post_id)
    {
        if ($post_id <= 0) {
            return null;
        }

        $plan = get_post_meta($post_id, "_wpaw_plan", true);
        return is_array($plan) ? $plan : null;
    }

    /**
     * Save post config to post meta.
     *
     * @param int   $post_id     Post ID.
     * @param array $post_config Post config.
     * @return bool Success.
     */
    public function save_post_config($post_id, $post_config)
    {
        if ($post_id <= 0) {
            return false;
        }

        return update_post_meta($post_id, "_wpaw_post_config", $post_config) !==
            false;
    }

    /**
     * Get post config from post meta.
     *
     * @param int $post_id Post ID.
     * @return array Post config.
     */
    public function get_post_config($post_id)
    {
        if ($post_id <= 0) {
            return $this->get_default_post_config();
        }

        $config = get_post_meta($post_id, "_wpaw_post_config", true);
        return is_array($config)
            ? wp_parse_args($config, $this->get_default_post_config())
            : $this->get_default_post_config();
    }

    /**
     * Get default post config.
     *
     * @return array Default config.
     */
    public function get_default_post_config()
    {
        $settings = get_option("wp_agentic_writer_settings", []);

        return [
            "article_length" => "medium",
            "language" => "auto",
            "tone" => "",
            "audience" => "",
            "experience_level" => "general",
            "include_images" => true,
            "web_search" => false,
            "default_mode" => "writing",
            "focus_keyword" => "",
            "seo_focus_keyword" => "",
            "seo_secondary_keywords" => "",
            "seo_meta_description" => "",
            "seo_enabled" => false,
        ];
    }

    /**
     * Migrate legacy chat history from post meta to session.
     *
     * @param int    $post_id    Post ID.
     * @param string $session_id Optional session ID to use. If not provided and no
     *                           session exists, creates a new one.
     * @return string|false Session ID used for migration, or false on failure.
     */
    public function migrate_legacy_chat_history($post_id, $session_id = "")
    {
        if ($post_id <= 0) {
            return false;
        }

        $legacy_history = get_post_meta($post_id, "_wpaw_chat_history", true);
        if (empty($legacy_history) || !is_array($legacy_history)) {
            return $session_id ?: true; // Nothing to migrate, return existing or true
        }

        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();

        // Try to find existing session for this post if no session_id provided
        $sessions = [];
        if (empty($session_id)) {
            $sessions = $manager->get_sessions_for_post($post_id);
        }

        if (!empty($sessions)) {
            // Append to existing session
            $session = $sessions[0];
            $existing_messages = $session["messages"] ?? [];

            // Merge legacy messages (avoid duplicates based on content hash)
            $existing_hashes = [];
            foreach ($existing_messages as $msg) {
                $existing_hashes[] = $this->get_message_hash($msg);
            }

            foreach ($legacy_history as $msg) {
                $hash = $this->get_message_hash($msg);
                if (!in_array($hash, $existing_hashes, true)) {
                    $existing_messages[] = $msg;
                }
            }

            $manager->update_messages(
                $session["session_id"],
                $existing_messages,
            );
            $migrated_session_id = $session["session_id"];
        } else {
            // Create new session with legacy messages
            $new_session_id = $manager->create_session([
                "post_id" => $post_id,
                "messages" => $legacy_history,
                "title" => "Migrated from legacy",
            ]);
            $migrated_session_id = is_string($new_session_id)
                ? $new_session_id
                : (!empty($session_id)
                    ? $session_id
                    : "");
        }

        // Delete legacy meta after successful migration and mark as migrated.
        delete_post_meta($post_id, "_wpaw_chat_history");
        update_post_meta(
            $post_id,
            "_wpaw_chat_history_migrated",
            current_time("mysql"),
        );

        return $migrated_session_id;
    }

    /**
     * Get hash for message deduplication.
     *
     * @param array $message Message.
     * @return string Hash.
     */
    private function get_message_hash($message)
    {
        $content = $message["content"] ?? "";
        $role = $message["role"] ?? "";
        return md5($role . ":" . $content);
    }

    /**
     * Merge context arrays while preserving nested JSON objects.
     *
     * @since 0.2.3
     * @param array $base  Existing context.
     * @param array $patch Context patch.
     * @return array Merged context.
     */
    private function merge_context_recursive($base, $patch)
    {
        foreach ($patch as $key => $value) {
            if (
                is_array($value) &&
                isset($base[$key]) &&
                is_array($base[$key]) &&
                !wp_is_numeric_array($value)
            ) {
                $base[$key] = $this->merge_context_recursive(
                    $base[$key],
                    $value,
                );
            } else {
                $base[$key] = $value;
            }
        }

        return $base;
    }

    /**
     * Clear context for a session and post.
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID.
     * @return bool Success.
     */
    public function clear_context($session_id, $post_id = 0)
    {
        $manager = WP_Agentic_Writer_Conversation_Manager::get_instance();

        // Clear specific session if provided
        if ($session_id) {
            $manager->update_messages($session_id, []);
        } elseif ($post_id > 0) {
            // No session_id provided - clear all active sessions for this post
            $sessions = $manager->get_sessions_for_post($post_id);
            foreach ($sessions as $session) {
                if (
                    !empty($session["session_id"]) &&
                    "active" === ($session["status"] ?? "")
                ) {
                    $manager->update_messages($session["session_id"], []);
                }
            }
        }

        // Clear post meta if post_id provided
        if ($post_id > 0) {
            delete_post_meta($post_id, "_wpaw_plan");
            delete_post_meta($post_id, "_wpaw_memory");
            delete_post_meta($post_id, "_wpaw_chat_history");
            // Keep _wpaw_post_config as it's user settings
        }

        return true;
    }

    /**
     * Get context summary for display.
     *
     * @param array $context Context data.
     * @return string Human-readable summary.
     */
    public function get_context_summary($context)
    {
        $parts = [];

        // Message count
        $msg_count = count($context["messages"] ?? []);
        $parts[] = sprintf(
            _n("%d message", "%d messages", $msg_count, "wp-agentic-writer"),
            $msg_count,
        );

        // Plan status
        if (!empty($context["plan"])) {
            $sections = count($context["plan"]["sections"] ?? []);
            $parts[] = sprintf(
                _n(
                    "%d section in plan",
                    "%d sections in plan",
                    $sections,
                    "wp-agentic-writer",
                ),
                $sections,
            );
        } else {
            $parts[] = "No plan";
        }

        // Focus keyword
        if (!empty($context["focus_keyword"])) {
            $parts[] = "Focus: " . $context["focus_keyword"];
        }

        return implode(" | ", $parts);
    }

    /**
     * Build context for AI prompt.
     *
     * @param array $context Context data.
     * @param int   $max_messages Maximum messages to include.
     * @return array Messages for AI.
     */
    public function build_ai_context($context, $max_messages = 20)
    {
        $messages = $context["messages"] ?? [];

        // Limit to most recent messages
        if (count($messages) > $max_messages) {
            $messages = array_slice($messages, -$max_messages);
        }

        // Add context summary if available
        $context_summary = [
            "role" => "system",
            "content" =>
                "Current context: " . $this->get_context_summary($context),
        ];

        return array_merge([$context_summary], $messages);
    }
}
