// Copyright 2026 Tree xie.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::connection::{
    AccessMode, RedisClientDescription, SlowLogEntry, get_connection_manager, get_server, get_servers,
};
use crate::db::get_search_history_manager;
use crate::error::Error;
use crate::helpers::unix_ts;
use crate::states::server::event::{ServerEvent, ServerTask};
use crate::states::server::history::{ValueHistoryEntry, push_history};
use crate::states::server::stat::{RedisInfo, get_metrics_cache};
use crate::states::{QueryMode, get_session_option};
use ahash::AHashMap;
use ahash::AHashSet;
use bytes::Bytes;
use gpui::SharedString;
use gpui::prelude::*;
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;
use tracing::{debug, error, info};
use uuid::Uuid;
use value::{KeyType, RedisValue, RedisValueData};

pub mod cluster;
pub mod event;
pub mod hash;
pub mod history;
pub mod json;
pub mod key;
pub mod list;
pub mod persistence;
pub mod sentinel;
pub mod set;
pub mod stat;
pub mod stream;
pub mod string;
pub mod value;
pub mod zset;

type Result<T, E = Error> = std::result::Result<T, E>;

// Constants for state management
const MAX_ERROR_MESSAGES: usize = 10; // Maximum error messages to keep in memory
/// Error message with categorization and timestamp
#[derive(Debug, Clone)]
pub struct ErrorMessage {
    /// Category of error (e.g., task name like "ping", "scan_keys")
    pub category: SharedString,

    /// Human-readable error message
    pub message: SharedString,
}

/// Redis server connection status
#[derive(Clone, PartialEq, Default, Debug)]
pub enum RedisServerStatus {
    /// Server is idle and ready for operations
    #[default]
    Idle,

    /// Server is loading initial data (connecting, fetching metadata)
    Loading,
}

/// Main state management for Redis server operations
///
/// This struct manages:
/// - Server connection and metadata (version, latency, dbsize)
/// - Key scanning and tree structure
/// - Selected key and its value
/// - Error message history
/// - Async task spawning and coordination
#[derive(Debug, Clone, Default)]
pub struct ZedisServerState {
    redis_info: Option<RedisInfo>,
    last_slow_logs_checked_at: i64,
    last_slow_log_count: usize,
    slow_logs: Vec<SlowLogEntry>,

    /// Whether the terminal is open
    terminal: bool,

    /// Currently selected server id
    server_id: SharedString,

    /// Search history
    search_history: Vec<SharedString>,

    /// Total number of databases
    databases: usize,

    /// Currently selected database
    db: usize,

    /// Access mode
    access_mode: AccessMode,

    /// Whether the server supports ReJSON module
    supports_rejson: bool,

    /// Whether the server has the RediSearch module loaded. Gates the
    /// "Search" entry in the Tools dropdown so unrelated servers don't
    /// surface a useless menu item.
    supports_search: bool,

    /// Query mode (All/Prefix/Exact) for key filtering
    query_mode: QueryMode,

    /// Whether to soft wrap the editor
    soft_wrap: bool,

    /// Current server status
    server_status: RedisServerStatus,

    /// Total number of keys in the database (from DBSIZE command)
    dbsize: Option<u64>,

    /// Number of Redis nodes (master, replica) for cluster info
    nodes: (usize, usize),
    /// Description of the nodes
    nodes_description: Arc<RedisClientDescription>,

    /// Redis server version string
    version: SharedString,

    /// List of all configured servers
    // servers: Option<Vec<RedisServer>>,

    /// Currently selected key name
    key: Option<SharedString>,

    /// Value data for the currently selected key
    value: Option<RedisValue>,

    // ===== Key scanning state =====
    /// Search keyword for filtering keys
    keyword: SharedString,

    /// SCAN cursors for cluster nodes (one per node)
    cursors: Option<Vec<u64>>,

    /// Whether a scan operation is in progress
    scanning: bool,

    /// Whether the current scan has completed
    scan_completed: bool,

    /// Number of scan iterations performed
    scan_times: usize,

    /// Unique ID for current key tree (changes when keys are reloaded)
    key_tree_id: SharedString,

    /// Set of prefixes that have been scanned (for lazy loading folders)
    loaded_prefixes: AHashSet<SharedString>,

    /// Map of all loaded keys and their types
    keys: AHashMap<SharedString, KeyType>,

    /// Parallel map of per-key TTL in seconds, populated alongside `keys`
    /// during SCAN. `-1` = no expiry, `-2` = missing (key vanished between
    /// SCAN and the TTL pipeline). Kept separate from `keys` so older code
    /// paths that read `&AHashMap<SharedString, KeyType>` keep compiling.
    key_ttls: AHashMap<SharedString, i64>,

    /// In-memory write history per string key. Each entry is the bytes
    /// that were just overwritten by a SET, newest first. Capped at
    /// [`history::VALUE_HISTORY_CAPACITY`] per key and cleared on key
    /// delete or server switch. Never persisted to disk.
    value_history: AHashMap<SharedString, VecDeque<ValueHistoryEntry>>,

    // ===== Error tracking =====
    /// Recent error messages (limited to MAX_ERROR_MESSAGES)
    error_messages: Arc<RwLock<Vec<ErrorMessage>>>,
}

impl ZedisServerState {
    /// Create a new server state instance
    pub fn new() -> Self {
        Self::default()
    }

    /// Reset all scan-related state (clears keys, cursors, etc.)
    ///
    /// Called when switching servers or starting a new scan
    pub fn reset_scan(&mut self, cx: &mut Context<Self>) {
        self.keyword = SharedString::default();
        self.cursors = None;
        self.keys.clear();
        self.key_ttls.clear();
        self.key_tree_id = Uuid::now_v7().to_string().into();
        self.scanning = false;
        self.scan_completed = false;
        self.scan_times = 0;
        self.loaded_prefixes.clear();
        cx.emit(ServerEvent::KeyScanReset);
        cx.emit(ServerEvent::KeyTreeUpdated);
    }

    /// If the currently-tracked server has been removed from the
    /// configured server list, drop our reference to it and clean up
    /// the cached client / metrics. Without this the heartbeat keeps
    /// firing against a phantom id and logs `Redis config not found`
    /// once per tick.
    pub fn clear_if_removed(&mut self, cx: &mut Context<Self>) {
        if self.server_id.is_empty() {
            return;
        }
        let still_exists = get_servers()
            .map(|servers| servers.iter().any(|s| s.id == self.server_id.as_ref()))
            .unwrap_or(true);
        if still_exists {
            return;
        }
        get_connection_manager().remove_client(&self.server_id, self.db);
        get_metrics_cache().remove_server(self.server_id.as_str());
        self.reset(cx);
    }

    /// Reset all state when switching to a different server
    fn reset(&mut self, cx: &mut Context<Self>) {
        self.server_id = SharedString::default();
        self.version = SharedString::default();
        self.nodes = (0, 0);
        self.keys.clear();
        self.key_ttls.clear();
        // History is scoped to the current server session; drop it when
        // we move to a different server to avoid restoring stale bytes
        // into the wrong target.
        self.value_history.clear();
        self.key_tree_id = SharedString::default();
        self.nodes_description = Arc::new(RedisClientDescription::default());
        self.dbsize = None;
        self.key = None;
        self.redis_info = None;
        self.value = None;
        self.reset_scan(cx);
        self.terminal = false;
        self.last_slow_logs_checked_at = 0;
        self.last_slow_log_count = 0;
        self.slow_logs.clear();
    }

    /// Add new keys with their types to the key map (deduplicating automatically)
    ///
    /// If any new keys were added, generates a new tree ID to trigger UI refresh
    fn extend_keys(&mut self, keys: Vec<(SharedString, SharedString, i64)>) {
        self.keys.reserve(keys.len());
        self.key_ttls.reserve(keys.len());
        let mut insert_count = 0;

        for (key, key_type, ttl_secs) in keys {
            let kt = KeyType::from(key_type.as_ref());
            self.keys
                .entry(key.clone())
                .and_modify(|existing| {
                    if *existing == KeyType::Unknown && kt != KeyType::Unknown {
                        *existing = kt;
                    }
                })
                .or_insert_with(|| {
                    insert_count += 1;
                    kt
                });
            // Always update the freshest TTL (it counts down) — replace
            // existing rather than insert_with.
            self.key_ttls.insert(key, ttl_secs);
        }

        // Update tree ID only if new keys were added
        if insert_count != 0 {
            self.key_tree_id = Uuid::now_v7().to_string().into();
        }
    }

    /// Add an error message to the history and emit error event
    ///
    /// Maintains a rolling window of MAX_ERROR_MESSAGES most recent errors.
    /// Includes server_id and db context for easier debugging.
    fn add_error_message(&mut self, category: String, message: String, cx: &mut Context<Self>) {
        let mut guard = self.error_messages.write();

        // Remove oldest error if at capacity
        if guard.len() >= MAX_ERROR_MESSAGES {
            guard.remove(0);
        }

        let server_name = get_server(&self.server_id).map(|s| s.name).unwrap_or_default();
        let context_message: SharedString = if server_name.is_empty() {
            format!("[{category}] {message}").into()
        } else {
            format!("[{category}] [{server_name}:{}] {message}", self.db).into()
        };

        let info = ErrorMessage {
            category: category.into(),
            message: context_message,
        };
        guard.push(info.clone());
        self.emit_error_notification(info.message.clone(), cx);
        cx.emit(ServerEvent::ErrorOccurred(info));
    }
    /// Spawn an async background task with error handling
    ///
    /// This is the core async task dispatcher that:
    /// 1. Emits a Spawn event for UI feedback
    /// 2. Runs the task in a background thread pool
    /// 3. Captures errors and adds them to error history
    /// 4. Calls the callback with the result
    ///
    /// # Type Parameters
    /// * `T` - The success return type of the task
    /// * `Fut` - The future type returned by the task closure
    ///
    /// # Arguments
    /// * `name` - Task identifier for logging and error tracking
    /// * `task` - Async closure that performs the operation
    /// * `callback` - Called with the result when task completes
    /// * `cx` - Context for spawning and state updates
    fn spawn<T, Fut>(
        &mut self,
        name: ServerTask,
        task: impl FnOnce() -> Fut + Send + 'static,
        callback: impl FnOnce(&mut Self, Result<T>, &mut Context<Self>) + Send + 'static,
        cx: &mut Context<Self>,
    ) where
        T: Send + 'static,
        Fut: Future<Output = Result<T>> + Send + 'static,
    {
        cx.emit(ServerEvent::TaskStarted(name.clone()));
        debug!(name = name.as_str(), "Spawning background task");
        let server_id = self.server_id.clone();
        let start = Instant::now();

        cx.spawn(async move |handle, cx| {
            // Run task in background executor (thread pool)
            let task = cx.background_spawn(async move { task().await });
            let result: Result<T> = task.await;

            // Update state with result on main thread
            handle.update(cx, move |this, cx| {
                if let Err(e) = &result {
                    error!(
                        task = name.as_str(),
                        server_id = server_id.as_str(),
                        error = %e,
                        "Task failed"
                    );
                    // only add error message if the server id is the same as the current server id
                    // ignore refresh redis info error
                    if this.server_id == server_id && name != ServerTask::RefreshRedisInfo {
                        this.add_error_message(name.as_str().to_string(), e.to_string(), cx);
                    }
                }
                callback(this, result, cx);
                let latency = start.elapsed();
                if name != ServerTask::RefreshRedisInfo {
                    info!(
                        task = name.as_str(),
                        server_id = server_id.as_str(),
                        latency_ms = latency.as_millis(),
                        "Task completed"
                    );
                }
            })
        })
        .detach();
    }

    fn try_get_mut_key_value(&mut self) -> Option<(SharedString, &mut RedisValue)> {
        let key = self.key.as_ref().filter(|k| !k.is_empty())?.clone();
        let value = self.value.as_mut()?;
        if value.is_busy() {
            return None;
        }
        Some((key, value))
    }

    // ===== Public accessor methods =====

    pub fn is_terminal(&self) -> bool {
        self.terminal
    }

    pub fn toggle_terminal(&mut self, cx: &mut Context<Self>) {
        self.terminal = !self.terminal;
        cx.emit(ServerEvent::TerminalToggled(self.terminal));
    }

    /// Check if the server is currently busy with an operation
    pub fn is_busy(&self) -> bool {
        !matches!(self.server_status, RedisServerStatus::Idle)
    }

    /// Get the current key tree ID (changes when keys are reloaded)
    pub fn key_tree_id(&self) -> &str {
        &self.key_tree_id
    }

    /// Get the search history
    pub fn search_history(&self) -> Vec<SharedString> {
        self.search_history.clone()
    }

    pub fn clear_search_history(&mut self, _cx: &mut Context<Self>) {
        self.search_history.clear();
    }

    /// Get whether the server supports ReJSON module
    pub fn supports_rejson(&self) -> bool {
        self.supports_rejson
    }

    /// Whether the server has the RediSearch module loaded.
    pub fn supports_search(&self) -> bool {
        self.supports_search
    }

    /// Get whether the server is readonly
    pub fn readonly(&self) -> bool {
        matches!(self.access_mode, AccessMode::StrictReadOnly | AccessMode::SafeMode)
    }
    pub fn toggle_readonly(&mut self, cx: &mut Context<Self>) {
        if matches!(self.access_mode, AccessMode::StrictReadOnly) {
            self.add_error_message(
                "toggle_readonly".to_string(),
                "Strict read-only mode, cannot be toggled".to_string(),
                cx,
            );
            return;
        }
        if self.access_mode == AccessMode::ReadWrite {
            self.access_mode = AccessMode::SafeMode;
        } else {
            self.access_mode = AccessMode::ReadWrite;
        }
        cx.emit(ServerEvent::ServerInfoUpdated);
    }

    /// Set the query mode (All/Prefix/Exact)
    pub fn set_query_mode(&mut self, mode: QueryMode, _cx: &mut Context<Self>) {
        self.query_mode = mode;
    }
    /// Set whether to soft wrap the editor
    pub fn set_soft_wrap(&mut self, soft_wrap: bool, cx: &mut Context<Self>) {
        self.soft_wrap = soft_wrap;
        cx.emit(ServerEvent::SoftWrapToggled(self.soft_wrap));
    }
    /// Get the current query mode (All/Prefix/Exact)
    pub fn query_mode(&self) -> QueryMode {
        self.query_mode
    }

    /// Check if the current scan has completed
    pub fn scan_completed(&self) -> bool {
        self.scan_completed
    }

    /// Check if a scan is currently in progress
    pub fn scanning(&self) -> bool {
        self.scanning
    }

    /// Get the total database size (number of keys)
    pub fn dbsize(&self) -> Option<u64> {
        self.dbsize
    }

    /// Get the count of scanned/loaded keys
    pub fn scan_count(&self) -> usize {
        self.keys.len()
    }

    /// Get the last measured latency to the server
    pub fn redis_info(&self) -> Option<&RedisInfo> {
        self.redis_info.as_ref()
    }

    /// Get the slow logs
    pub fn slow_logs(&self) -> &Vec<SlowLogEntry> {
        &self.slow_logs
    }
    /// Get the last slow log count
    pub fn last_slow_log_count(&self) -> usize {
        self.last_slow_log_count
    }

    /// Get cluster node counts (master, replica)
    pub fn nodes(&self) -> (usize, usize) {
        self.nodes
    }
    /// Get the description of the nodes
    pub fn nodes_description(&self) -> Arc<RedisClientDescription> {
        self.nodes_description.clone()
    }

    /// Get the Redis server version string
    pub fn version(&self) -> &str {
        &self.version
    }

    /// Returns true when the connected Redis server supports per-field hash TTL
    /// commands (HEXPIRE, HTTL, HPERSIST), introduced in Redis 7.4.
    pub fn supports_hash_field_ttl(&self) -> bool {
        use semver::Version;
        Version::parse(self.version.as_ref())
            .map(|v| v >= Version::new(7, 4, 0))
            .unwrap_or(false)
    }

    /// Returns true when the server is at least Redis 6.0, where ACL
    /// (the `ACL USERS / GETUSER / SETUSER / WHOAMI` family) was
    /// introduced. Drives Tools-menu visibility so users on older
    /// servers don't see a non-functional entry.
    pub fn supports_acl(&self) -> bool {
        use semver::Version;
        Version::parse(self.version.as_ref())
            .map(|v| v >= Version::new(6, 0, 0))
            .unwrap_or(false)
    }

    /// Returns true when the server is at least Redis 7.0, where
    /// Functions (the `FUNCTION LIST / LOAD / DELETE / FCALL` family)
    /// were introduced as the successor to EVAL scripts.
    pub fn supports_functions(&self) -> bool {
        use semver::Version;
        Version::parse(self.version.as_ref())
            .map(|v| v >= Version::new(7, 0, 0))
            .unwrap_or(false)
    }

    /// Get the currently selected server id
    pub fn server_id(&self) -> &str {
        &self.server_id
    }
    /// Get the currently selected database
    pub fn db(&self) -> usize {
        self.db
    }

    /// Get the total number of databases
    pub fn databases(&self) -> usize {
        self.databases
    }

    /// Get whether to soft wrap the editor
    pub fn soft_wrap(&self) -> bool {
        self.soft_wrap
    }

    /// Get the currently selected key name
    pub fn key(&self) -> Option<SharedString> {
        self.key.clone()
    }
    /// Get the map of all loaded keys and their types
    pub fn key_ttls(&self) -> &AHashMap<SharedString, i64> {
        &self.key_ttls
    }
    pub fn keys(&self) -> &AHashMap<SharedString, KeyType> {
        &self.keys
    }

    /// Get the value data for the currently selected key
    pub fn value(&self) -> Option<&RedisValue> {
        self.value.as_ref()
    }

    /// Get the in-memory value history for `key` (newest first), if any.
    /// Returns `None` rather than an empty slice so callers can easily
    /// distinguish "never written" from "written and rolled back to 0".
    pub fn value_history_for(&self, key: &SharedString) -> Option<&VecDeque<ValueHistoryEntry>> {
        self.value_history.get(key).filter(|d| !d.is_empty())
    }

    /// Append the bytes about to be overwritten to the history ring buffer.
    /// Called from the SET paths in value.rs right before mutating state.
    /// Empty `bytes` are still recorded — "was empty" is itself meaningful
    /// history to roll back to.
    pub(super) fn push_value_history(&mut self, key: SharedString, bytes: Bytes) {
        let buffer = self.value_history.entry(key).or_default();
        push_history(buffer, ValueHistoryEntry { bytes, at: unix_ts() });
    }

    /// Drop all history entries for `key`. Called when the key is deleted
    /// so we don't pile up dangling versions of a name the server no
    /// longer knows about.
    pub(super) fn clear_value_history_for(&mut self, key: &SharedString) {
        self.value_history.remove(key);
    }

    pub fn set_search_history(&mut self, history: Vec<SharedString>) {
        self.search_history = history;
    }

    /// Select and connect to a Redis server
    ///
    /// This initiates a connection and loads server metadata:
    /// - Database size (DBSIZE)
    /// - Server version
    /// - Latency measurement (PING)
    /// - Cluster node counts
    ///
    /// If query_mode is QueryMode::All, automatically starts scanning all keys.
    ///
    /// # Arguments
    /// * `server_id` - Server id to connect to
    /// * `db` - Database to connect to
    /// * `cx` - Context for spawning async tasks and state updates
    pub fn select(&mut self, server_id: SharedString, db: usize, cx: &mut Context<Self>) {
        // Only proceed if selecting a different server
        if self.server_id != server_id || self.db != db {
            get_metrics_cache().remove_server(self.server_id.as_str());
            self.reset(cx);
            self.server_id = server_id.clone();
            self.db = db;

            let (query_mode, soft_wrap) = get_session_option(&server_id)
                .map(|option| {
                    let mode = option
                        .query_mode
                        .as_deref()
                        .and_then(|s| QueryMode::from_str(s).ok())
                        .unwrap_or_default();

                    let wrap = option.soft_wrap.unwrap_or(true);

                    // 返回一个元组，包含所有需要更新的值
                    (mode, wrap)
                })
                .unwrap_or((QueryMode::All, true));
            self.query_mode = query_mode;
            self.soft_wrap = soft_wrap;

            debug!(server_id = self.server_id.as_str(), "Selecting server");
            let search_history_manager = get_search_history_manager();
            if let Ok(history) = search_history_manager.records(server_id.as_str()) {
                self.search_history = history;
            }
            cx.emit(ServerEvent::ServerSelected(server_id));

            // Set loading state
            self.server_status = RedisServerStatus::Loading;
            self.scanning = true;
            cx.notify();

            let server_id_clone = self.server_id.clone();
            let counting_server_id = server_id_clone.clone();
            let db = self.db;

            self.spawn(
                ServerTask::SelectServer,
                move || async move {
                    let client = get_connection_manager().get_client(&server_id_clone, db).await?;

                    // Gather server metadata
                    let dbsize = client.dbsize().await?;
                    let version = client.version().to_string();
                    let nodes = client.nodes();
                    let nodes_description = client.nodes_description();
                    let databases = client.databases();
                    let access_mode = client.access_mode();
                    let supports_rejson = client.supports_rejson();
                    let supports_search = client.supports_search();
                    Ok((
                        dbsize,
                        nodes,
                        nodes_description,
                        version,
                        databases,
                        access_mode,
                        supports_rejson,
                        supports_search,
                    ))
                },
                move |this, result, cx| {
                    // Ignore if user switched to a different server while loading
                    if this.server_id != counting_server_id {
                        return;
                    }

                    // Update metadata if successful
                    if let Ok((
                        dbsize,
                        nodes,
                        nodes_description,
                        version,
                        databases,
                        access_mode,
                        supports_rejson,
                        supports_search,
                    )) = result
                    {
                        this.dbsize = Some(dbsize);
                        this.nodes = nodes;
                        this.nodes_description = Arc::new(nodes_description);
                        this.version = version.into();
                        this.databases = databases;
                        this.access_mode = access_mode;
                        this.supports_rejson = supports_rejson;
                        this.supports_search = supports_search;
                    };

                    let server_id = this.server_id.clone();
                    this.server_status = RedisServerStatus::Idle;
                    cx.emit(ServerEvent::ServerInfoUpdated);
                    let is_exact_mode = this.query_mode == QueryMode::Exact;
                    if is_exact_mode {
                        this.scanning = false;
                    }
                    cx.notify();
                    // Auto-scan keys if not exact mode
                    if !is_exact_mode {
                        this.scan_keys(server_id, SharedString::default(), cx);
                    }
                },
                cx,
            );
        }
    }
}
