//! Auto-optimization strategy framework.
//!
//! Selects an `OptimizationStrategy` per entity based on priority,
//! access patterns, and file size. Periodically re-evaluates strategies
//! as access patterns evolve.

use std::sync::Arc;

use tracing::{debug, warn};

use crate::db::SqliteDb;
use crate::scaling::priority::Priority;

/// Thresholds for promoting entities to higher indexing modes.
const ACCESS_PROMOTE_THRESHOLD: i64 = 3;
/// Files accessed within this window (seconds) are considered "hot".
const HOT_ACCESS_WINDOW_SECS: i64 = 3600; // 1 hour

/// Storage tiers for entity data.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageTier {
    /// Full in-memory: graph + vector + hot cache.
    Hot,
    /// In-memory graph, vector on disk.
    Warm,
    /// Metadata only, graph on-demand.
    Cold,
    /// Not indexed; load on first access.
    Archive,
}

impl StorageTier {
    pub fn as_str(&self) -> &'static str {
        match self {
            StorageTier::Hot => "hot",
            StorageTier::Warm => "warm",
            StorageTier::Cold => "cold",
            StorageTier::Archive => "archive",
        }
    }
}

/// The optimization strategy chosen for a particular file.
#[derive(Debug, Clone)]
pub struct OptimizationStrategy {
    pub file_path: String,
    pub priority: Priority,
    pub storage_tier: StorageTier,
    pub indexing_mode: String,
    pub bloom_indexed: bool,
    /// Human-readable reasoning for debugging.
    pub reasoning: String,
}

/// Selects optimization strategies based on priority + access patterns.
pub struct AutoOptimizer {
    sqlite: Arc<SqliteDb>,
}

impl AutoOptimizer {
    pub fn new(sqlite: Arc<SqliteDb>) -> Self {
        Self { sqlite }
    }

    /// Compute the optimization strategy for a single file.
    pub async fn strategy_for_file(&self, file_path: &str) -> OptimizationStrategy {
        let meta = self
            .sqlite
            .get_index_metadata(file_path)
            .await
            .ok()
            .flatten();

        let (priority, access_count, last_accessed, _index_mode, size_bytes) = match meta {
            Some(m) => (
                Priority::from_i32(m.priority),
                m.access_count,
                m.last_accessed,
                m.index_mode.clone(),
                m.size_bytes,
            ),
            None => (Priority::Reference, 0, 0, "unknown".to_string(), 0),
        };

        let now = chrono::Utc::now().timestamp();
        let recently_accessed = (now - last_accessed) < HOT_ACCESS_WINDOW_SECS;
        let frequently_accessed = access_count >= ACCESS_PROMOTE_THRESHOLD;

        // Determine storage tier and indexing mode.
        let (storage_tier, target_mode, bloom, reasoning) = match priority {
            Priority::Critical => (
                StorageTier::Hot,
                "full",
                true,
                format!("Critical priority: full indexing + hot cache (size={}B)", size_bytes),
            ),
            Priority::Important => {
                if frequently_accessed && recently_accessed {
                    (
                        StorageTier::Hot,
                        "full",
                        true,
                        format!("Important + hot access ({} accesses): promoted to full", access_count),
                    )
                } else {
                    (
                        StorageTier::Warm,
                        "graph_only",
                        true,
                        format!("Important priority: graph-only indexing ({} accesses)", access_count),
                    )
                }
            }
            Priority::Reference => {
                if frequently_accessed {
                    (
                        StorageTier::Warm,
                        "graph_only",
                        true,
                        format!("Reference but frequently accessed ({} accesses): promoted to graph", access_count),
                    )
                } else {
                    (
                        StorageTier::Cold,
                        "metadata_only",
                        false,
                        format!("Reference priority: metadata-only ({} accesses)", access_count),
                    )
                }
            }
            Priority::Archive => {
                if frequently_accessed && recently_accessed {
                    (
                        StorageTier::Cold,
                        "metadata_only",
                        false,
                        format!("Archive but hot access ({} accesses): promoted to metadata", access_count),
                    )
                } else {
                    (
                        StorageTier::Archive,
                        "skip",
                        false,
                        format!("Archive priority: skipped ({} accesses)", access_count),
                    )
                }
            }
        };

        OptimizationStrategy {
            file_path: file_path.to_string(),
            priority,
            storage_tier,
            indexing_mode: target_mode.to_string(),
            bloom_indexed: bloom,
            reasoning,
        }
    }

    /// Batch re-optimize: evaluate all tracked files and return strategies
    /// for any that should change their indexing mode.
    pub async fn re_optimize_all(&self) -> Vec<OptimizationStrategy> {
        let stats = match self.sqlite.index_metadata_stats().await {
            Ok(s) => s,
            Err(e) => {
                warn!(error = %e, "Failed to get index metadata stats for re-optimization");
                return vec![];
            }
        };

        debug!(
            total = stats.total,
            "Starting re-optimization of {} files",
            stats.total
        );

        // Fetch all tracked file paths from SQLite.
        let file_paths = match self.sqlite.all_index_metadata_paths().await {
            Ok(paths) => paths,
            Err(e) => {
                warn!(error = %e, "Failed to get file paths for re-optimization");
                return vec![];
            }
        };

        let mut changes = Vec::new();
        for path in &file_paths {
            let strategy = self.strategy_for_file(path).await;

            // Check if the strategy differs from current state.
            if let Ok(Some(meta)) = self.sqlite.get_index_metadata(path).await {
                if meta.index_mode != strategy.indexing_mode {
                    debug!(
                        file = %path,
                        from = %meta.index_mode,
                        to = %strategy.indexing_mode,
                        reason = %strategy.reasoning,
                        "Strategy change recommended"
                    );
                    changes.push(strategy);
                }
            }
        }

        if !changes.is_empty() {
            debug!(
                changes = changes.len(),
                "Re-optimization complete, {} files need strategy updates",
                changes.len()
            );
        }

        changes
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_storage_tier_as_str() {
        assert_eq!(StorageTier::Hot.as_str(), "hot");
        assert_eq!(StorageTier::Warm.as_str(), "warm");
        assert_eq!(StorageTier::Cold.as_str(), "cold");
        assert_eq!(StorageTier::Archive.as_str(), "archive");
    }

    #[test]
    fn test_optimization_strategy_debug() {
        let strategy = OptimizationStrategy {
            file_path: "src/main.rs".into(),
            priority: Priority::Critical,
            storage_tier: StorageTier::Hot,
            indexing_mode: "full".into(),
            bloom_indexed: true,
            reasoning: "Critical priority file".into(),
        };
        let debug = format!("{:?}", strategy);
        assert!(debug.contains("src/main.rs"));
        assert!(debug.contains("Critical"));
        assert!(debug.contains("Hot"));
    }

    #[test]
    fn test_access_promote_threshold() {
        assert_eq!(ACCESS_PROMOTE_THRESHOLD, 3);
    }

    #[test]
    fn test_hot_access_window() {
        assert_eq!(HOT_ACCESS_WINDOW_SECS, 3600);
    }
}
