//! Scope-aware pattern resolution with inheritance.
//!
//! Implements the inheritance logic for resolving patterns across
//! the scope hierarchy. Supports multiple override policies for
//! different use cases.

use std::collections::HashSet;

use super::{ScopeContext, ScopeId, ScopeLevel};
use crate::learning::LearnedPattern;

/// Policy for how scope overrides work.
///
/// Determines how patterns from different scope levels interact
/// when resolving the effective pattern for a concept.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OverridePolicy {
    /// More specific scopes replace inherited patterns (default).
    ///
    /// When a project defines a pattern, it completely overrides
    /// any team or org patterns for that concept.
    #[default]
    Replace,

    /// More specific scopes merge with inherited patterns.
    ///
    /// All matching patterns from all scopes are returned,
    /// deduplicated by normalized_pattern.
    Merge,

    /// Inheritance disabled; only local scope applies.
    ///
    /// Only patterns from the current (most specific) scope
    /// are returned, ignoring all inherited patterns.
    NoInherit,
}

impl std::fmt::Display for OverridePolicy {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            OverridePolicy::Replace => write!(f, "replace"),
            OverridePolicy::Merge => write!(f, "merge"),
            OverridePolicy::NoInherit => write!(f, "no-inherit"),
        }
    }
}

impl std::str::FromStr for OverridePolicy {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "replace" => Ok(OverridePolicy::Replace),
            "merge" => Ok(OverridePolicy::Merge),
            "no-inherit" | "noinherit" | "none" => Ok(OverridePolicy::NoInherit),
            _ => Err(format!(
                "Invalid override policy '{}'. Valid values: replace, merge, no-inherit",
                s
            )),
        }
    }
}

/// Resolves patterns across scope hierarchy.
///
/// Provides scope-aware pattern resolution with configurable
/// inheritance policies.
pub struct ScopeResolver {
    context: ScopeContext,
    policy: OverridePolicy,
}

impl ScopeResolver {
    /// Create a new resolver with default policy (Replace).
    pub fn new(context: ScopeContext) -> Self {
        Self { context, policy: OverridePolicy::default() }
    }

    /// Create a resolver with a specific override policy.
    pub fn with_policy(context: ScopeContext, policy: OverridePolicy) -> Self {
        Self { context, policy }
    }

    /// Get the current scope context.
    pub fn context(&self) -> &ScopeContext {
        &self.context
    }

    /// Get the current override policy.
    pub fn policy(&self) -> OverridePolicy {
        self.policy
    }

    /// Resolve patterns for a concept, respecting inheritance.
    ///
    /// Returns patterns that match the given concept path, filtered
    /// according to the scope hierarchy and override policy.
    pub fn resolve_patterns<'a>(
        &self,
        patterns: &'a [LearnedPattern],
        concept_path: &str,
    ) -> Vec<&'a LearnedPattern> {
        let chain = self.context.inheritance_chain();

        if chain.is_empty() {
            // No scope context, return all patterns for concept
            return patterns
                .iter()
                .filter(|p| p.claim_template.subject_template == concept_path)
                .collect();
        }

        match self.policy {
            OverridePolicy::NoInherit => {
                // Only current (most specific) scope
                self.filter_by_scope(patterns, chain.first(), concept_path)
            }
            OverridePolicy::Replace => {
                // Most specific scope that has patterns wins
                for scope in &chain {
                    let matches = self.filter_by_scope(patterns, Some(scope), concept_path);
                    if !matches.is_empty() {
                        return matches;
                    }
                }
                Vec::new()
            }
            OverridePolicy::Merge => {
                // All scopes, deduplicated by normalized_pattern
                let mut seen = HashSet::new();
                chain
                    .iter()
                    .flat_map(|scope| self.filter_by_scope(patterns, Some(scope), concept_path))
                    .filter(|p| seen.insert(&p.normalized_pattern))
                    .collect()
            }
        }
    }

    /// Resolve all patterns, not filtered by concept.
    ///
    /// Returns all patterns visible in the current scope hierarchy.
    pub fn resolve_all_patterns<'a>(
        &self,
        patterns: &'a [LearnedPattern],
    ) -> Vec<&'a LearnedPattern> {
        let chain = self.context.inheritance_chain();

        if chain.is_empty() {
            return patterns.iter().collect();
        }

        match self.policy {
            OverridePolicy::NoInherit => {
                // Only current scope
                if let Some(scope) = chain.first() {
                    patterns.iter().filter(|p| self.pattern_matches_scope(p, scope)).collect()
                } else {
                    Vec::new()
                }
            }
            OverridePolicy::Replace | OverridePolicy::Merge => {
                // All patterns from all scopes in hierarchy
                let mut seen = HashSet::new();
                chain
                    .iter()
                    .flat_map(|scope| {
                        patterns.iter().filter(|p| self.pattern_matches_scope(p, scope))
                    })
                    .filter(|p| seen.insert(&p.id))
                    .collect()
            }
        }
    }

    /// Filter patterns by scope and concept path.
    fn filter_by_scope<'a>(
        &self,
        patterns: &'a [LearnedPattern],
        scope: Option<&ScopeId>,
        concept_path: &str,
    ) -> Vec<&'a LearnedPattern> {
        patterns
            .iter()
            .filter(|p| {
                let scope_matches = scope.is_some_and(|s| self.pattern_matches_scope(p, s));
                scope_matches && p.claim_template.subject_template == concept_path
            })
            .collect()
    }

    /// Check if a pattern belongs to a specific scope.
    fn pattern_matches_scope(&self, pattern: &LearnedPattern, scope: &ScopeId) -> bool {
        pattern.scope_level == scope.level
            && pattern.scope_id.as_deref() == Some(scope.name.as_str())
    }

    /// Get patterns only at a specific scope level.
    ///
    /// Ignores inheritance; returns only patterns explicitly at the given level.
    pub fn patterns_at_level<'a>(
        &self,
        patterns: &'a [LearnedPattern],
        level: ScopeLevel,
    ) -> Vec<&'a LearnedPattern> {
        let scope_name = match level {
            ScopeLevel::Project => self.context.project.as_deref(),
            ScopeLevel::Team => self.context.team.as_deref(),
            ScopeLevel::Organization => self.context.organization.as_deref(),
        };

        if let Some(name) = scope_name {
            let scope = ScopeId::new(level, name);
            patterns.iter().filter(|p| self.pattern_matches_scope(p, &scope)).collect()
        } else {
            Vec::new()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::learning::{ClaimTemplate, ValueType};
    use crate::types::Language;

    fn make_pattern(subject: &str, scope_level: ScopeLevel, scope_id: &str) -> LearnedPattern {
        // Use unique normalized pattern including scope info to ensure merge mode works
        let normalized = format!("normalized pattern for {}", scope_id);
        let mut pattern = LearnedPattern::new(
            "example code",
            normalized,
            ClaimTemplate::new(subject, "version", ValueType::Text, "description"),
            Language::Rust,
            "project-hash",
            0.9,
        );
        pattern.scope_level = scope_level;
        pattern.scope_id = Some(scope_id.to_string());
        pattern
    }

    #[test]
    fn test_resolve_replace_policy() {
        let patterns = vec![
            make_pattern("tls/min_version", ScopeLevel::Organization, "acme"),
            make_pattern("tls/min_version", ScopeLevel::Project, "api-gateway"),
        ];

        let ctx =
            ScopeContext::new(Some("api-gateway".to_string()), None, Some("acme".to_string()));
        let resolver = ScopeResolver::new(ctx);

        let resolved = resolver.resolve_patterns(&patterns, "tls/min_version");

        // Project-level pattern should win (replace policy)
        assert_eq!(resolved.len(), 1);
        assert_eq!(resolved[0].scope_level, ScopeLevel::Project);
    }

    #[test]
    fn test_resolve_merge_policy() {
        let patterns = vec![
            make_pattern("tls/min_version", ScopeLevel::Organization, "acme"),
            make_pattern("tls/min_version", ScopeLevel::Project, "api-gateway"),
        ];

        let ctx =
            ScopeContext::new(Some("api-gateway".to_string()), None, Some("acme".to_string()));
        let resolver = ScopeResolver::with_policy(ctx, OverridePolicy::Merge);

        let resolved = resolver.resolve_patterns(&patterns, "tls/min_version");

        // Both patterns should be returned
        assert_eq!(resolved.len(), 2);
    }

    #[test]
    fn test_resolve_no_inherit_policy() {
        let patterns = vec![
            make_pattern("tls/min_version", ScopeLevel::Organization, "acme"),
            make_pattern("tls/min_version", ScopeLevel::Project, "api-gateway"),
        ];

        let ctx =
            ScopeContext::new(Some("api-gateway".to_string()), None, Some("acme".to_string()));
        let resolver = ScopeResolver::with_policy(ctx, OverridePolicy::NoInherit);

        let resolved = resolver.resolve_patterns(&patterns, "tls/min_version");

        // Only project-level pattern (current scope)
        assert_eq!(resolved.len(), 1);
        assert_eq!(resolved[0].scope_level, ScopeLevel::Project);
    }

    #[test]
    fn test_resolve_inheritance_when_local_empty() {
        let patterns = vec![make_pattern("tls/min_version", ScopeLevel::Organization, "acme")];

        let ctx =
            ScopeContext::new(Some("api-gateway".to_string()), None, Some("acme".to_string()));
        let resolver = ScopeResolver::new(ctx);

        let resolved = resolver.resolve_patterns(&patterns, "tls/min_version");

        // Should inherit from org when project has no pattern
        assert_eq!(resolved.len(), 1);
        assert_eq!(resolved[0].scope_level, ScopeLevel::Organization);
    }

    #[test]
    fn test_patterns_at_level() {
        let patterns = vec![
            make_pattern("tls/min_version", ScopeLevel::Organization, "acme"),
            make_pattern("db/pool_size", ScopeLevel::Project, "api-gateway"),
        ];

        let ctx =
            ScopeContext::new(Some("api-gateway".to_string()), None, Some("acme".to_string()));
        let resolver = ScopeResolver::new(ctx);

        let org_patterns = resolver.patterns_at_level(&patterns, ScopeLevel::Organization);
        assert_eq!(org_patterns.len(), 1);
        assert_eq!(org_patterns[0].claim_template.subject_template, "tls/min_version");

        let proj_patterns = resolver.patterns_at_level(&patterns, ScopeLevel::Project);
        assert_eq!(proj_patterns.len(), 1);
        assert_eq!(proj_patterns[0].claim_template.subject_template, "db/pool_size");
    }

    #[test]
    fn test_override_policy_from_str() {
        assert_eq!("replace".parse::<OverridePolicy>().ok(), Some(OverridePolicy::Replace));
        assert_eq!("merge".parse::<OverridePolicy>().ok(), Some(OverridePolicy::Merge));
        assert_eq!("no-inherit".parse::<OverridePolicy>().ok(), Some(OverridePolicy::NoInherit));
        assert!("invalid".parse::<OverridePolicy>().is_err());
    }
}
