<?php
/**
 * Conversation Manager
 *
 * Handles session-based chat history with MySQL table storage.
 * Supports both post-linked and standalone sessions.
 *
 * @package WP_Agentic_Writer
 */

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

class WP_Agentic_Writer_Conversation_Manager
{
    /**
     * Singleton instance
     *
     * @var WP_Agentic_Writer_Conversation_Manager
     */
    private static $instance = null;

    /**
     * Database table name
     *
     * @var string
     */
    private $table_name;

    /**
     * Current session ID
     *
     * @var string
     */
    private $current_session_id = null;

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

    /**
     * Constructor
     */
    private function __construct()
    {
        global $wpdb;
        $this->table_name = $wpdb->prefix . "wpaw_conversations";
    }

    /**
     * Generate a unique session ID
     *
     * @return string
     */
    public function generate_session_id()
    {
        if (function_exists("wp_generate_uuid4")) {
            return str_replace("-", "", wp_generate_uuid4());
        }
        // Fallback for older WP versions (UUID v4 format, without hyphens).
        return str_replace("-", "", sprintf(
            "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
            wp_rand(0, 0xffff),
            wp_rand(0, 0xffff),
            wp_rand(0, 0xffff),
            wp_rand(0, 0x0fff) | 0x4000,
            wp_rand(0, 0x3fff) | 0x8000,
            wp_rand(0, 0xffff),
            wp_rand(0, 0xffff),
            wp_rand(0, 0xffff),
        ));
    }

    /**
     * Create a new conversation session
     *
     * @param array $data Session data.
     * @return string|WP_Error Session ID or error.
     */
    public function create_session($data = [])
    {
        global $wpdb;

        $session_id = $this->generate_session_id();
        $user_id = get_current_user_id();

        $result = $wpdb->insert(
            $this->table_name,
            [
                "session_id" => $session_id,
                "user_id" => $user_id,
                "post_id" => isset($data["post_id"])
                    ? (int) $data["post_id"]
                    : 0,
                "title" => isset($data["title"])
                    ? sanitize_text_field($data["title"])
                    : "",
                "focus_keyword" => isset($data["focus_keyword"])
                    ? sanitize_text_field($data["focus_keyword"])
                    : "",
                "messages" => isset($data["messages"])
                    ? json_encode($data["messages"])
                    : "[]",
                "context" => isset($data["context"])
                    ? json_encode($data["context"])
                    : "{}",
                "status" => "active",
            ],
            ["%s", "%d", "%d", "%s", "%s", "%s", "%s", "%s"],
        );

        if (false === $result) {
            return new WP_Error(
                "db_error",
                __("Failed to create session.", "wp-agentic-writer"),
                ["status" => 500],
            );
        }

        $this->current_session_id = $session_id;
        return $session_id;
    }

    /**
     * Check if current user can access a session
     *
     * @param string $session_id Session ID.
     * @return bool True if user can access.
     */
    public function current_user_can_access($session_id)
    {
        $session = $this->get_session($session_id);

        if (!$session) {
            return false;
        }

        $current_user_id = get_current_user_id();

        // User owns this session
        if ((int) $session["user_id"] === $current_user_id) {
            return true;
        }

        // For post-linked sessions, check if user can edit the post
        if (!empty($session["post_id"])) {
            $post_id = (int) $session["post_id"];
            if ($post_id > 0 && current_user_can("edit_post", $post_id)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get a session by session ID (with authorization check)
     *
     * @param string $session_id Session ID.
     * @return array|null Session data or null if not found/not authorized.
     */
    public function get_session($session_id)
    {
        global $wpdb;

        $session = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$this->table_name} WHERE session_id = %s",
                $session_id,
            ),
            ARRAY_A,
        );

        if (!$session) {
            return null;
        }

        // Decode JSON fields
        $session["messages"] = json_decode($session["messages"], true) ?: [];
        $session["context"] = json_decode($session["context"], true) ?: [];

        return $session;
    }

    /**
     * Get a session by session ID (public - for internal use only)
     * Use this only when authorization is handled separately
     *
     * @param string $session_id Session ID.
     * @return array|null Session data or null if not found.
     */
    public function get_session_unchecked($session_id)
    {
        global $wpdb;

        $session = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$this->table_name} WHERE session_id = %s",
                $session_id,
            ),
            ARRAY_A,
        );

        if (!$session) {
            return null;
        }

        // Decode JSON fields
        $session["messages"] = json_decode($session["messages"], true) ?: [];
        $session["context"] = json_decode($session["context"], true) ?: [];

        return $session;
    }

    /**
     * Get session by post ID
     *
     * @param int $post_id Post ID.
     * @return array|null Session data or null.
     */
    public function get_session_by_post_id($post_id)
    {
        global $wpdb;

        $session = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM {$this->table_name} WHERE post_id = %d AND status != 'archived' ORDER BY updated_at DESC LIMIT 1",
                $post_id,
            ),
            ARRAY_A,
        );

        if (!$session) {
            return null;
        }

        $session["messages"] = json_decode($session["messages"], true) ?: [];
        $session["context"] = json_decode($session["context"], true) ?: [];

        return $session;
    }

    /**
     * Get all active sessions for current user
     *
     * @param string $status Status filter (active, completed, archived).
     * @param int    $limit   Number of results.
     * @return array Sessions list.
     */
    public function get_user_sessions($status = "active", $limit = 20)
    {
        global $wpdb;
        $user_id = get_current_user_id();
        $posts_table = $wpdb->posts;

        $sessions = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT c.id, c.session_id, c.post_id, c.title, c.focus_keyword, c.status, c.created_at, c.updated_at,
					JSON_LENGTH(c.messages) as message_count,
					COALESCE(p.post_status, '') as post_status
				FROM {$this->table_name} c
				LEFT JOIN {$posts_table} p ON p.ID = c.post_id
				WHERE c.user_id = %d AND c.status = %s
				ORDER BY updated_at DESC
				LIMIT %d",
                $user_id,
                $status,
                $limit,
            ),
            ARRAY_A,
        );

        return $sessions ?: [];
    }

    /**
     * Get all uncompleted sessions (post_id = 0) for current user
     *
     * @param int $limit Number of results.
     * @return array Sessions list.
     */
    public function get_uncompleted_sessions($limit = 20)
    {
        global $wpdb;
        $user_id = get_current_user_id();

        $sessions = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, session_id, post_id, title, focus_keyword, status, created_at, updated_at,
					JSON_LENGTH(messages) as message_count
				FROM {$this->table_name}
				WHERE user_id = %d AND post_id = 0 AND status = 'active'
				ORDER BY updated_at DESC
				LIMIT %d",
                $user_id,
                $limit,
            ),
            ARRAY_A,
        );

        return $sessions ?: [];
    }

    /**
     * Update session messages
     *
     * @param string $session_id Session ID.
     * @param array  $messages    Messages array.
     * @return bool True on success.
     */
    public function update_messages($session_id, $messages)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "messages" => json_encode($messages),
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%s", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Update session context
     *
     * @param string $session_id Session ID.
     * @param array  $context    Context data.
     * @return bool True on success.
     */
    public function update_context($session_id, $context)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "context" => json_encode($context),
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%s", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Link session to a post
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID.
     * @return bool True on success.
     */
    public function link_to_post($session_id, $post_id)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "post_id" => (int) $post_id,
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%d", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Update session title
     *
     * @param string $session_id Session ID.
     * @param string $title      New title.
     * @return bool True on success.
     */
    public function update_title($session_id, $title)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "title" => sanitize_text_field($title),
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%s", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Update focus keyword
     *
     * @param string $session_id   Session ID.
     * @param string $focus_keyword Focus keyword.
     * @return bool True on success.
     */
    public function update_focus_keyword($session_id, $focus_keyword)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "focus_keyword" => sanitize_text_field($focus_keyword),
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%s", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Mark session as completed
     *
     * @param string $session_id Session ID.
     * @return bool True on success.
     */
    public function mark_completed($session_id)
    {
        global $wpdb;

        $result = $wpdb->update(
            $this->table_name,
            [
                "status" => "completed",
                "updated_at" => current_time("mysql"),
            ],
            ["session_id" => $session_id],
            ["%s", "%s"],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Delete a session
     *
     * @param string $session_id Session ID.
     * @return bool True on success.
     */
    public function delete_session($session_id)
    {
        global $wpdb;

        $result = $wpdb->delete(
            $this->table_name,
            ["session_id" => $session_id],
            ["%s"],
        );

        return false !== $result;
    }

    /**
     * Get or create session for post
     *
     * @param int $post_id Post ID (can be 0 for new posts).
     * @return array Session data with session_id.
     */
    public function get_or_create_session_for_post($post_id = 0)
    {
        // Try to find existing session for this post
        if ($post_id > 0) {
            $session = $this->get_session_by_post_id($post_id);
            if ($session) {
                return $session;
            }
        }

        // Create new session
        $session_id = $this->create_session(["post_id" => $post_id]);

        if (is_wp_error($session_id)) {
            return null;
        }

        return $this->get_session($session_id);
    }

    /**
     * Set current session ID
     *
     * @param string $session_id Session ID.
     */
    public function set_current_session($session_id)
    {
        $this->current_session_id = $session_id;
    }

    /**
     * Get current session ID
     *
     * @return string|null
     */
    public function get_current_session_id()
    {
        return $this->current_session_id;
    }

    /**
     * Get current session data
     *
     * @return array|null
     */
    public function get_current_session()
    {
        if (!$this->current_session_id) {
            return null;
        }
        return $this->get_session($this->current_session_id);
    }

    /**
     * Check if current session has post ID
     *
     * @return bool
     */
    public function current_session_has_post()
    {
        $session = $this->get_current_session();
        return $session && $session["post_id"] > 0;
    }

    /**
     * Check if editor has content (for auto-save decision)
     *
     * @param int $post_id Post ID.
     * @return bool True if post has content blocks.
     */
    public function post_has_content($post_id)
    {
        if ($post_id <= 0) {
            return false;
        }

        $post = get_post($post_id);
        if (!$post) {
            return false;
        }

        // Check if post has any blocks or content
        $blocks = parse_blocks($post->post_content);
        return !empty($blocks);
    }

    /**
     * Get all sessions for a specific post.
     *
     * @param int $post_id Post ID.
     * @return array Sessions array.
     */
    public function get_sessions_for_post($post_id)
    {
        global $wpdb;

        if ($post_id <= 0) {
            return [];
        }

        $sessions = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT *, JSON_LENGTH(messages) as message_count FROM {$this->table_name} WHERE post_id = %d ORDER BY updated_at DESC",
                $post_id,
            ),
            ARRAY_A,
        );

        // Decode JSON fields for each session
        foreach ($sessions as &$session) {
            $session["messages"] =
                json_decode($session["messages"], true) ?: [];
            $session["context"] = json_decode($session["context"], true) ?: [];
        }

        return $sessions ?: [];
    }

    /**
     * Acquire or refresh a session edit lock.
     *
     * Lock format: "timestamp:user_id:tab_id"
     * A lock is stale if its timestamp is older than $window seconds.
     *
     * @param string $session_id Session ID.
     * @param string $tab_id     Unique browser tab identifier.
     * @param int    $window     Lock expiry window in seconds (default 60).
     * @return array { locked: bool, holder?: array }
     */
    public function acquire_lock($session_id, $tab_id, $window = 60)
    {
        global $wpdb;

        $user_id = get_current_user_id();
        $now = time();

        // Read current lock
        $current_lock = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT edit_lock FROM {$this->table_name} WHERE session_id = %s",
                $session_id,
            ),
        );

        if ($current_lock) {
            $parts = explode(":", $current_lock);
            $lock_time = (int) ($parts[0] ?? 0);
            $lock_user = (int) ($parts[1] ?? 0);
            $lock_tab = $parts[2] ?? "";

            // Lock is still fresh AND held by a different tab
            if ($now - $lock_time < $window && $lock_tab !== $tab_id) {
                return [
                    "locked" => false,
                    "holder" => [
                        "user_id" => $lock_user,
                        "tab_id" => $lock_tab,
                        "since" => $lock_time,
                    ],
                ];
            }
        }

        // Acquire / refresh the lock
        $lock_value = "{$now}:{$user_id}:{$tab_id}";
        $wpdb->update(
            $this->table_name,
            ["edit_lock" => $lock_value],
            ["session_id" => $session_id],
            ["%s"],
            ["%s"],
        );

        return ["locked" => true];
    }

    /**
     * Release a session edit lock (only if held by this tab).
     *
     * @param string $session_id Session ID.
     * @param string $tab_id     Browser tab identifier.
     * @return bool True if released.
     */
    public function release_lock($session_id, $tab_id)
    {
        global $wpdb;

        $current_lock = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT edit_lock FROM {$this->table_name} WHERE session_id = %s",
                $session_id,
            ),
        );

        if (!$current_lock) {
            return true;
        }

        $parts = explode(":", $current_lock);
        $lock_tab = $parts[2] ?? "";

        if ($lock_tab !== $tab_id) {
            return false;
        }

        $wpdb->update(
            $this->table_name,
            ["edit_lock" => null],
            ["session_id" => $session_id],
            ["%s"],
            ["%s"],
        );

        return true;
    }

    /**
     * Check the lock status of a session without modifying it.
     *
     * @param string $session_id Session ID.
     * @param string $tab_id     Current tab identifier.
     * @param int    $window     Lock expiry window in seconds.
     * @return array { is_locked: bool, is_mine: bool, holder?: array }
     */
    public function check_lock($session_id, $tab_id = "", $window = 60)
    {
        global $wpdb;

        $now = time();

        $current_lock = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT edit_lock FROM {$this->table_name} WHERE session_id = %s",
                $session_id,
            ),
        );

        if (!$current_lock) {
            return ["is_locked" => false, "is_mine" => false];
        }

        $parts = explode(":", $current_lock);
        $lock_time = (int) ($parts[0] ?? 0);
        $lock_user = (int) ($parts[1] ?? 0);
        $lock_tab = $parts[2] ?? "";

        // Expired
        if ($now - $lock_time >= $window) {
            return ["is_locked" => false, "is_mine" => false];
        }

        $is_mine = $lock_tab === $tab_id;

        return [
            "is_locked" => true,
            "is_mine" => $is_mine,
            "holder" => [
                "user_id" => $lock_user,
                "tab_id" => $lock_tab,
                "since" => $lock_time,
            ],
        ];
    }
}
