use std::sync::Arc;

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

use crate::types::{LlmProvider, SearchResult};

use super::deduplicator::Deduplicator;
use super::grep_executor::GrepExecutor;
use super::plan_parser::PlanParser;
use super::result_ranker::ResultRanker;
use super::search::AgenticSearchConfig;

/// Hybrid search augments an existing set of candidate results by asking an
/// LLM whether the candidates are sufficient and, if not, generating
/// additional grep searches to fill the gaps.
pub struct HybridSearch {
    llm: Arc<dyn LlmProvider>,
    grep_executor: GrepExecutor,
    plan_parser: PlanParser,
    result_ranker: ResultRanker,
    deduplicator: Deduplicator,
    config: AgenticSearchConfig,
}

impl HybridSearch {
    pub fn new(
        llm: Arc<dyn LlmProvider>,
        grep_executor: GrepExecutor,
        plan_parser: PlanParser,
        result_ranker: ResultRanker,
        deduplicator: Deduplicator,
        config: AgenticSearchConfig,
    ) -> Self {
        Self {
            llm,
            grep_executor,
            plan_parser,
            result_ranker,
            deduplicator,
            config,
        }
    }

    /// Execute a hybrid search.
    ///
    /// 1. Ask the LLM whether `candidates` are sufficient for the `query`.
    /// 2. If yes, rank and return them.
    /// 3. If no, ask the LLM for additional grep patterns, execute them,
    ///    merge with the original candidates, deduplicate, and rank.
    #[instrument(skip(self, candidates), fields(candidate_count = candidates.len()))]
    pub async fn search(
        &self,
        query: &str,
        candidates: Vec<SearchResult>,
    ) -> Result<Vec<SearchResult>> {
        if !self.llm.is_available() {
            debug!("HybridSearch: LLM unavailable, returning candidates as-is");
            return Ok(candidates);
        }

        info!(query = query, "starting hybrid search");

        // ---- Step 1: Judge sufficiency ----
        let is_sufficient = self.judge_sufficiency(query, &candidates).await;

        if is_sufficient {
            debug!("candidates judged sufficient; ranking and returning");
            let ranked = self.result_ranker.rank(query, candidates).await?;
            return Ok(ranked);
        }

        debug!("candidates judged insufficient; generating additional searches");

        // ---- Step 2: Generate additional greps ----
        let additional_patterns = self.generate_additional_greps(query, &candidates).await?;

        if additional_patterns.is_empty() {
            warn!("LLM produced no additional grep patterns; returning original candidates");
            let ranked = self.result_ranker.rank(query, candidates).await?;
            return Ok(ranked);
        }

        // ---- Step 3: Execute additional greps ----
        let capped: Vec<String> = additional_patterns
            .into_iter()
            .take(self.config.max_grep_commands)
            .collect();

        debug!(count = capped.len(), "executing additional grep patterns");
        let grep_results = self.grep_executor.execute_many(&capped).await?;

        // ---- Step 4: Merge, deduplicate, rank ----
        let mut merged = candidates;
        merged.extend(grep_results);

        let deduped = self.deduplicator.deduplicate(merged);
        let ranked = self.result_ranker.rank(query, deduped).await?;

        info!(
            result_count = ranked.len(),
            "hybrid search complete"
        );

        Ok(ranked)
    }

    // ---- internal ----

    /// Ask the LLM whether the existing candidates are sufficient.
    async fn judge_sufficiency(&self, query: &str, candidates: &[SearchResult]) -> bool {
        let summary = Self::summarize_candidates(candidates);

        let system = "You are a search quality judge. Given a user query and a set of search \
                       results, determine if the results are sufficient to answer the query. \
                       Respond with a JSON object: {\"sufficient\": true} or {\"sufficient\": false}. \
                       Be strict: if the results seem incomplete or only tangentially related, \
                       say false.";

        let prompt = format!(
            "Query: \"{}\"\n\nExisting results ({} total):\n{}\n\n\
             Are these results sufficient to answer the query?",
            query,
            candidates.len(),
            summary,
        );

        match tokio::time::timeout(
            std::time::Duration::from_secs(30),
            self.llm.complete_with_system(system, &prompt),
        ).await {
            Ok(Ok(response)) => self.plan_parser.parse_sufficiency(&response),
            Ok(Err(e)) => {
                warn!(error = %e, "sufficiency check failed; assuming insufficient");
                false
            }
            Err(_) => {
                warn!("sufficiency check timed out after 30s; assuming insufficient");
                false
            }
        }
    }

    /// Ask the LLM to generate additional grep patterns to fill gaps.
    async fn generate_additional_greps(
        &self,
        query: &str,
        candidates: &[SearchResult],
    ) -> Result<Vec<String>> {
        let summary = Self::summarize_candidates(candidates);

        let system = "You are a code search expert. Given a user query and the results found \
                       so far, suggest additional grep search patterns that would help find \
                       the missing information. Respond with a JSON array of grep pattern strings. \
                       Each pattern should be a valid grep basic regex. Focus on patterns that \
                       would find code definitions, usages, or related implementations. \
                       Example: [\"fn process_event\", \"impl EventHandler\", \"struct Config\"]";

        let prompt = format!(
            "Query: \"{}\"\n\nResults found so far ({} total):\n{}\n\n\
             What additional grep patterns should we search for?",
            query,
            candidates.len(),
            summary,
        );

        match tokio::time::timeout(
            std::time::Duration::from_secs(30),
            self.llm.complete_with_system(system, &prompt),
        ).await {
            Ok(Ok(response)) => {
                match self.plan_parser.parse_additional_greps(&response) {
                    Ok(patterns) => {
                        debug!(count = patterns.len(), "LLM generated additional patterns");
                        Ok(patterns)
                    }
                    Err(e) => {
                        warn!(error = %e, "failed to parse additional greps from LLM");
                        Ok(Vec::new())
                    }
                }
            }
            Ok(Err(e)) => {
                warn!(error = %e, "LLM call for additional greps failed");
                Ok(Vec::new())
            }
            Err(_) => {
                warn!("LLM call for additional greps timed out after 30s");
                Ok(Vec::new())
            }
        }
    }

    /// Build a compact text summary of the candidates for inclusion in prompts.
    fn summarize_candidates(candidates: &[SearchResult]) -> String {
        let mut summary = String::new();
        let display_limit = 20;

        for (i, result) in candidates.iter().take(display_limit).enumerate() {
            let line_info = result
                .line_number
                .map(|n| format!(":{n}"))
                .unwrap_or_default();

            let content_preview = if result.content.len() > 120 {
                format!("{}...", &result.content[..120])
            } else {
                result.content.clone()
            };

            summary.push_str(&format!(
                "  [{}] {}{} | {}\n",
                i, result.file_path, line_info, content_preview
            ));
        }

        if candidates.len() > display_limit {
            summary.push_str(&format!(
                "  ... and {} more results\n",
                candidates.len() - display_limit
            ));
        }

        summary
    }
}
