use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};

use anyhow::Result;
use tracing::{debug, info};

use crate::db::SqliteDb;
use crate::types::{QueryType, SearchStrategy};

/// Selects a search strategy for a classified query, using learned data
/// from past searches when available, falling back to sensible defaults.
///
/// Implements exploration/exploitation via a deterministic counter:
/// every `explore_rate`-th call uses the default strategy to gather
/// fresh data, while other calls exploit the learned best strategy
/// (if it meets confidence thresholds).
pub struct StrategySelector {
    db: Arc<SqliteDb>,
    /// Monotonic counter for deterministic exploration.
    explore_counter: AtomicU64,
    /// Fraction of calls that explore (0.1 = 10%).
    explore_rate: f64,
    /// Minimum samples before trusting a learned strategy.
    min_confidence_samples: i64,
    /// Minimum success rate before trusting a learned strategy.
    min_success_rate: f64,
}

impl StrategySelector {
    pub fn new(db: Arc<SqliteDb>) -> Self {
        Self {
            db,
            explore_counter: AtomicU64::new(0),
            explore_rate: 0.1,
            min_confidence_samples: 30,
            min_success_rate: 0.6,
        }
    }

    /// Choose the best strategy for a given query type.
    ///
    /// Decision flow:
    /// 1. If `should_explore()` → use default strategy (exploration)
    /// 2. Else fetch `get_learned_strategy_full()`
    /// 3. If sample_count >= 30 AND success_rate >= 0.6 → use learned (exploitation)
    /// 4. Else → use default strategy (low confidence)
    pub async fn select_strategy(
        &self,
        query_type: &QueryType,
        max_affordable_tier: u8,
    ) -> Result<SearchStrategy> {
        let pattern = format!("{:?}", query_type);

        // Exploration: periodically use default to gather fresh data
        if self.should_explore() {
            let default = self.default_strategy(query_type);
            let capped = self.cap_strategy(default.clone(), max_affordable_tier);
            debug!(
                query_type = %pattern,
                strategy = ?capped,
                "Exploration: using default strategy"
            );
            return Ok(capped);
        }

        // Try the learned strategy with confidence checks
        if let Some(learned) = self.db.get_learned_strategy_full(&pattern).await? {
            if learned.sample_count >= self.min_confidence_samples
                && learned.success_rate >= self.min_success_rate
            {
                if let Some(strategy) = Self::parse_strategy(&learned.best_strategy) {
                    let capped = self.cap_strategy(strategy, max_affordable_tier);
                    info!(
                        query_type = %pattern,
                        learned_strategy = %learned.best_strategy,
                        success_rate = learned.success_rate,
                        samples = learned.sample_count,
                        capped = ?capped,
                        "Using learned strategy (high confidence)"
                    );
                    return Ok(capped);
                }
            } else {
                debug!(
                    query_type = %pattern,
                    success_rate = learned.success_rate,
                    samples = learned.sample_count,
                    "Learned strategy below confidence threshold, using default"
                );
            }
        }

        // Fall back to defaults.
        let default = self.default_strategy(query_type);
        let capped = self.cap_strategy(default.clone(), max_affordable_tier);
        debug!(
            query_type = %pattern,
            strategy = ?capped,
            "Using default strategy"
        );
        Ok(capped)
    }

    /// Deterministic exploration: every N-th call returns true.
    /// No `rand` crate needed — uses a monotonic counter.
    fn should_explore(&self) -> bool {
        let count = self.explore_counter.fetch_add(1, Ordering::Relaxed);
        let interval = (1.0 / self.explore_rate) as u64;
        count.is_multiple_of(interval)
    }

    /// Return the default strategy for a query type when no learned data exists.
    fn default_strategy(&self, query_type: &QueryType) -> SearchStrategy {
        match query_type {
            QueryType::ExactMatch => SearchStrategy::Tier1ThenTier2,
            QueryType::Semantic => SearchStrategy::Tier1ThenTier2ThenTier3,
            QueryType::Pattern => SearchStrategy::Tier2Only,
            QueryType::Architecture => SearchStrategy::GraphQuery,
            QueryType::Dependency => SearchStrategy::GraphQuery,
        }
    }

    /// Ensure the selected strategy does not exceed the affordable tier.
    fn cap_strategy(&self, strategy: SearchStrategy, max_tier: u8) -> SearchStrategy {
        match max_tier {
            0 => SearchStrategy::Tier1Only,
            1 => SearchStrategy::Tier1Only,
            2 => match strategy {
                SearchStrategy::Tier1Only => SearchStrategy::Tier1Only,
                SearchStrategy::Tier1ThenTier2 => SearchStrategy::Tier1ThenTier2,
                SearchStrategy::Tier2Only => SearchStrategy::Tier2Only,
                SearchStrategy::GraphQuery => SearchStrategy::GraphQuery,
                SearchStrategy::Tier1ThenTier2ThenTier3 => SearchStrategy::Tier1ThenTier2,
                SearchStrategy::Tier3Only => SearchStrategy::Tier2Only,
            },
            _ => strategy, // 3+: no cap
        }
    }

    /// Parse a SearchStrategy from its Debug representation.
    fn parse_strategy(s: &str) -> Option<SearchStrategy> {
        match s.trim() {
            "Tier1Only" => Some(SearchStrategy::Tier1Only),
            "Tier1ThenTier2" => Some(SearchStrategy::Tier1ThenTier2),
            "Tier1ThenTier2ThenTier3" => Some(SearchStrategy::Tier1ThenTier2ThenTier3),
            "Tier2Only" => Some(SearchStrategy::Tier2Only),
            "Tier3Only" => Some(SearchStrategy::Tier3Only),
            "GraphQuery" => Some(SearchStrategy::GraphQuery),
            _ => None,
        }
    }
}

// ===========================================================================
// Tests
// ===========================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db::SqliteDb;
    use tempfile::NamedTempFile;

    async fn make_selector() -> (StrategySelector, Arc<SqliteDb>, NamedTempFile) {
        let tmp = NamedTempFile::new().unwrap();
        let db = Arc::new(SqliteDb::new(tmp.path()).unwrap());
        let selector = StrategySelector::new(Arc::clone(&db));
        (selector, db, tmp)
    }

    #[tokio::test]
    async fn test_exploration_rate_deterministic() {
        let (selector, _db, _tmp) = make_selector().await;

        // First call (counter=0): 0 % 10 == 0 → explore
        assert!(selector.should_explore());
        // Next 9 calls: should NOT explore
        for _ in 0..9 {
            assert!(!selector.should_explore());
        }
        // 11th call (counter=10): 10 % 10 == 0 → explore again
        assert!(selector.should_explore());
    }

    #[tokio::test]
    async fn test_confidence_threshold_fallback() {
        let (selector, db, _tmp) = make_selector().await;

        // Insert a learned strategy with low sample count (below threshold)
        db.upsert_learned_strategy("Semantic", "Tier1Only", 0.9, 50, 1, 5).await.unwrap();

        // Should fall back to default since sample_count (5) < min_confidence_samples (30)
        // Skip first call (exploration)
        let _ = selector.select_strategy(&QueryType::Semantic, 3).await.unwrap();
        let strategy = selector.select_strategy(&QueryType::Semantic, 3).await.unwrap();
        // Default for Semantic is Tier1ThenTier2ThenTier3
        assert_eq!(strategy, SearchStrategy::Tier1ThenTier2ThenTier3);
    }

    #[tokio::test]
    async fn test_success_rate_threshold() {
        let (selector, db, _tmp) = make_selector().await;

        // Insert a learned strategy with enough samples but low success rate
        db.upsert_learned_strategy("Semantic", "Tier1Only", 0.3, 50, 1, 100).await.unwrap();

        // Skip first call (exploration)
        let _ = selector.select_strategy(&QueryType::Semantic, 3).await.unwrap();
        let strategy = selector.select_strategy(&QueryType::Semantic, 3).await.unwrap();
        // Should fall back since success_rate (0.3) < min_success_rate (0.6)
        assert_eq!(strategy, SearchStrategy::Tier1ThenTier2ThenTier3);
    }

    #[tokio::test]
    async fn test_high_confidence_uses_learned() {
        let (selector, db, _tmp) = make_selector().await;

        // Insert a learned strategy that meets all thresholds
        db.upsert_learned_strategy("ExactMatch", "Tier2Only", 0.85, 30, 2, 50).await.unwrap();

        // Skip first call (exploration)
        let _ = selector.select_strategy(&QueryType::ExactMatch, 3).await.unwrap();
        let strategy = selector.select_strategy(&QueryType::ExactMatch, 3).await.unwrap();
        assert_eq!(strategy, SearchStrategy::Tier2Only);
    }

    #[tokio::test]
    async fn test_default_strategies() {
        let (selector, _db, _tmp) = make_selector().await;

        // Skip exploration call
        let _ = selector.select_strategy(&QueryType::Pattern, 3).await.unwrap();

        let strategy = selector.select_strategy(&QueryType::Pattern, 3).await.unwrap();
        assert_eq!(strategy, SearchStrategy::Tier2Only);
    }
}
