//! Research execution for the Research Agent.
//!
//! Handles the actual research process: fetching documentation, extracting
//! claims, and validating quality before ingestion.

use std::time::Duration;

use stemedb_core::types::ObjectValue;
use tracing::{debug, info, instrument, warn};

use super::gap_store::GapRecord;
use super::helpers::{
    calculate_confidence, default_documentation_sources, determine_scheme_from_url,
    determine_value_and_predicate, extract_normative_statements, normalize_topic,
};
use super::quality::{QualityReport, QualityValidator};
use super::{GapResearchResult, ResearchOutcome};
use crate::AphoriaError;

/// Configuration for the research process.
#[derive(Debug, Clone)]
pub struct ResearchConfig {
    /// HTTP timeout for fetching documentation.
    pub fetch_timeout: Duration,

    /// Maximum content length to process (bytes).
    pub max_content_length: usize,

    /// Minimum confidence for accepting claims.
    pub min_confidence: f32,

    /// Whether to use strict validation.
    pub strict_validation: bool,

    /// Search patterns for common documentation sites.
    pub search_patterns: Vec<DocumentationSource>,
}

impl Default for ResearchConfig {
    fn default() -> Self {
        Self {
            fetch_timeout: Duration::from_secs(30),
            max_content_length: 500_000, // 500KB
            min_confidence: 0.7,
            strict_validation: true,
            search_patterns: default_documentation_sources(),
        }
    }
}

/// A documentation source to search for claims.
#[derive(Debug, Clone)]
pub struct DocumentationSource {
    /// Name of the source (e.g., "Redis Docs").
    pub name: String,

    /// Base URL pattern.
    pub url_pattern: String,

    /// Topics this source covers.
    pub topics: Vec<String>,

    /// Authority tier for claims from this source.
    pub tier: u8,
}

/// A claim extracted from research.
#[derive(Debug, Clone)]
pub struct ResearchedClaim {
    /// Subject path (e.g., `vendor://redis/max_memory/policy`).
    pub subject: String,

    /// Predicate.
    pub predicate: String,

    /// Extracted value.
    pub value: ObjectValue,

    /// Human-readable description.
    pub description: String,

    /// Source URL where the claim was found.
    pub source_url: String,

    /// Section or heading where the claim was found.
    pub source_section: String,

    /// Confidence in this extraction (0.0 to 1.0).
    pub confidence: f32,

    /// Authority tier (0=Regulatory, 1=Clinical, 2=Observational).
    pub tier: u8,
}

/// Result of researching a single topic.
#[derive(Debug)]
pub struct ResearchResult {
    /// The topic that was researched.
    pub topic: String,

    /// Claims extracted from research.
    pub claims: Vec<ResearchedClaim>,

    /// Quality validation report.
    pub quality_report: QualityReport,

    /// URLs that were searched.
    pub urls_searched: Vec<String>,

    /// Errors encountered during research.
    pub errors: Vec<String>,
}

/// The Research Agent.
pub struct Researcher {
    config: ResearchConfig,
    validator: QualityValidator,
}

impl Researcher {
    /// Create a new researcher with default configuration.
    pub fn new() -> Self {
        Self { config: ResearchConfig::default(), validator: QualityValidator::default() }
    }

    /// Create a researcher with custom configuration.
    pub fn with_config(config: ResearchConfig) -> Self {
        let validator = if config.strict_validation {
            QualityValidator::strict()
        } else {
            QualityValidator::new(config.min_confidence)
        };

        Self { config, validator }
    }

    /// Research a batch of gaps.
    #[instrument(skip(self, gaps), fields(gap_count = gaps.len()))]
    pub fn research_gaps(&self, gaps: &[&GapRecord]) -> ResearchOutcome {
        let mut outcome = ResearchOutcome::empty();
        outcome.gaps_analyzed = gaps.len();

        for gap in gaps {
            let result = self.research_gap(gap);
            outcome.results.push(result.clone());

            if result.success {
                outcome.gaps_filled += 1;
                outcome.assertions_created += result.assertions_created;
            } else {
                outcome.gaps_failed.push(gap.key.clone());
            }
        }

        info!(
            analyzed = outcome.gaps_analyzed,
            filled = outcome.gaps_filled,
            assertions = outcome.assertions_created,
            failed = outcome.gaps_failed.len(),
            "Research complete"
        );

        outcome
    }

    /// Research a single gap.
    fn research_gap(&self, gap: &GapRecord) -> GapResearchResult {
        info!(topic = %gap.topic, predicate = %gap.predicate, "Researching gap");

        // Find relevant documentation sources
        let sources = self.find_sources_for_topic(&gap.topic);

        if sources.is_empty() {
            debug!(topic = %gap.topic, "No documentation sources found for topic");
            return GapResearchResult {
                gap: gap.key.clone(),
                success: false,
                assertions_created: 0,
                quality_report: None,
                error: Some("No documentation sources found for topic".to_string()),
            };
        }

        let mut all_claims = Vec::new();
        let mut errors = Vec::new();

        // Fetch and parse each source
        for source in &sources {
            let url = self.build_url(source, &gap.topic);

            match self.fetch_and_extract(&url, &gap.topic, &gap.predicate, source.tier) {
                Ok(claims) => {
                    debug!(
                        url = %url,
                        claims = claims.len(),
                        "Extracted claims from source"
                    );
                    all_claims.extend(claims);
                }
                Err(e) => {
                    debug!(url = %url, error = %e, "Failed to fetch source");
                    errors.push(format!("{}: {}", url, e));
                }
            }
        }

        if all_claims.is_empty() {
            return GapResearchResult {
                gap: gap.key.clone(),
                success: false,
                assertions_created: 0,
                quality_report: None,
                error: Some(format!("No claims extracted. Errors: {}", errors.join("; "))),
            };
        }

        // Validate quality
        let quality_report = self.validator.validate(&all_claims);
        let passed_claims = self.validator.filter_passed(all_claims);

        if passed_claims.is_empty() {
            return GapResearchResult {
                gap: gap.key.clone(),
                success: false,
                assertions_created: 0,
                quality_report: Some(quality_report),
                error: Some("All claims failed quality validation".to_string()),
            };
        }

        GapResearchResult {
            gap: gap.key.clone(),
            success: true,
            assertions_created: passed_claims.len(),
            quality_report: Some(quality_report),
            error: None,
        }
    }

    /// Find documentation sources relevant to a topic.
    fn find_sources_for_topic(&self, topic: &str) -> Vec<&DocumentationSource> {
        let topic_lower = topic.to_lowercase();

        self.config
            .search_patterns
            .iter()
            .filter(|source| {
                source.topics.iter().any(|t| {
                    let t_lower = t.to_lowercase();
                    topic_lower.contains(&t_lower) || t_lower.contains(&topic_lower)
                })
            })
            .collect()
    }

    /// Build a URL for a documentation source and topic.
    fn build_url(&self, source: &DocumentationSource, topic: &str) -> String {
        // Extract the main topic word
        let topic_word = topic.split('/').next().unwrap_or(topic).to_lowercase().replace('_', "-");

        source.url_pattern.replace("{topic}", &topic_word)
    }

    /// Fetch a URL and extract claims from it.
    fn fetch_and_extract(
        &self,
        url: &str,
        topic: &str,
        predicate: &str,
        tier: u8,
    ) -> Result<Vec<ResearchedClaim>, AphoriaError> {
        // Fetch content
        let response = ureq::get(url)
            .timeout(self.config.fetch_timeout)
            .call()
            .map_err(|e| AphoriaError::Storage(format!("HTTP fetch failed: {}", e)))?;

        // Check content length
        let content_length =
            response.header("content-length").and_then(|h| h.parse::<usize>().ok()).unwrap_or(0);

        if content_length > self.config.max_content_length {
            return Err(AphoriaError::Storage(format!(
                "Content too large: {} bytes (max {})",
                content_length, self.config.max_content_length
            )));
        }

        let content = response
            .into_string()
            .map_err(|e| AphoriaError::Storage(format!("Failed to read response: {}", e)))?;

        // Extract claims from content
        let claims = self.extract_claims_from_content(&content, url, topic, predicate, tier);

        Ok(claims)
    }

    /// Extract claims from documentation content.
    fn extract_claims_from_content(
        &self,
        content: &str,
        url: &str,
        topic: &str,
        predicate: &str,
        tier: u8,
    ) -> Vec<ResearchedClaim> {
        let mut claims = Vec::new();

        // Determine the scheme based on URL
        let scheme = determine_scheme_from_url(url);

        // Extract normative statements
        let statements = extract_normative_statements(content, topic);

        for (section, statement, keyword_strength) in statements {
            // Build subject path
            let subject = format!("{}://{}", scheme, normalize_topic(topic));

            // Determine value based on keyword
            let (value, effective_predicate) = determine_value_and_predicate(&statement, predicate);

            // Calculate confidence based on keyword strength and content quality
            let confidence = calculate_confidence(keyword_strength, &statement, content.len());

            claims.push(ResearchedClaim {
                subject,
                predicate: effective_predicate,
                value,
                description: statement,
                source_url: url.to_string(),
                source_section: section,
                confidence,
                tier,
            });
        }

        claims
    }

    /// Get validated claims ready for ingestion.
    pub fn get_validated_claims(&self, gaps: &[&GapRecord]) -> Vec<ResearchedClaim> {
        let mut all_validated = Vec::new();

        for gap in gaps {
            let sources = self.find_sources_for_topic(&gap.topic);

            for source in sources {
                let url = self.build_url(source, &gap.topic);

                if let Ok(claims) =
                    self.fetch_and_extract(&url, &gap.topic, &gap.predicate, source.tier)
                {
                    let validated = self.validator.filter_passed(claims);
                    all_validated.extend(validated);
                }
            }
        }

        all_validated
    }
}

impl Default for Researcher {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
#[path = "researcher_tests.rs"]
mod tests;
