use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoutingRule {
    pub model_pattern: String,
    pub provider: String,
    pub max_tokens: Option<u32>,
    pub temperature: Option<f64>,
}

#[derive(Clone, Default)]
pub struct RoutingEngine {
    rules: Vec<RoutingRule>,
    fallback_chain: Vec<String>,
}

impl RoutingEngine {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_rules(mut self, rules: Vec<RoutingRule>) -> Self {
        self.rules = rules;
        self
    }

    pub fn with_fallback(mut self, chain: Vec<String>) -> Self {
        self.fallback_chain = chain;
        self
    }

    pub fn route(&self, model: &str) -> RouteDecision {
        let lower = model.to_lowercase();

        for rule in &self.rules {
            if glob_match(&rule.model_pattern.to_lowercase(), &lower) {
                return RouteDecision {
                    provider: rule.provider.clone(),
                    model: model.to_string(),
                    max_tokens: rule.max_tokens,
                    temperature: rule.temperature,
                };
            }
        }

        if let Some(first) = self.fallback_chain.first() {
            RouteDecision {
                provider: first.clone(),
                model: model.to_string(),
                max_tokens: None,
                temperature: None,
            }
        } else {
            RouteDecision {
                provider: "ollama".to_string(),
                model: model.to_string(),
                max_tokens: None,
                temperature: None,
            }
        }
    }
}

#[derive(Clone, Debug)]
pub struct RouteDecision {
    pub provider: String,
    pub model: String,
    pub max_tokens: Option<u32>,
    pub temperature: Option<f64>,
}

fn glob_match(pattern: &str, name: &str) -> bool {
    if !pattern.contains('*') { return pattern == name; }
    let parts: Vec<&str> = pattern.split('*').collect();
    // Multi-* support: first must be prefix, last must be suffix, each
    // interior piece must appear in order. Fixes the iter-9 finding
    // where gpt-*-large* silently fell through to an exact-match path.
    // Also removes the dead `parts.len() == 1` branch that accessed
    // parts[1] and would panic if ever reached (unreachable today
    // since split('*') on a string containing '*' always yields ≥2).
    if !name.starts_with(parts[0]) || !name.ends_with(parts.last().unwrap()) { return false; }
    let mut cursor = parts[0].len();
    parts[1..parts.len() - 1].iter().all(|mid| {
        name[cursor..].find(mid).map(|pos| { cursor += pos + mid.len(); true }).unwrap_or(false)
    })
}

impl Default for RoutingRule {
    fn default() -> Self {
        Self {
            model_pattern: "*".to_string(),
            provider: "ollama".to_string(),
            max_tokens: None,
            temperature: None,
        }
    }
}

#[cfg(test)]
mod glob_match_tests {
    use super::glob_match;

    #[test] fn exact_match() { assert!(glob_match("gpt-oss:120b", "gpt-oss:120b")); }
    #[test] fn exact_mismatch() { assert!(!glob_match("a", "b")); }
    #[test] fn leading_wildcard() { assert!(glob_match("*:120b", "gpt-oss:120b")); }
    #[test] fn trailing_wildcard() { assert!(glob_match("gpt-oss:*", "gpt-oss:120b")); }
    #[test] fn bare_wildcard() { assert!(glob_match("*", "anything")); }
    #[test] fn multi_wildcard_in_order() { assert!(glob_match("gpt-*-oss-*", "gpt-4-oss-120b")); }
    #[test] fn multi_wildcard_wrong_order() { assert!(!glob_match("b*a*", "abba")); }
    #[test] fn multi_wildcard_panic_safety() {
        // Regression: earlier impl had an unreachable `parts.len() == 1`
        // branch that indexed parts[1] — would panic if ever hit. Now
        // the split('*') invariant guarantees ≥2 parts when * present,
        // and we handle all N-part cases explicitly.
        assert!(glob_match("a*b*c", "abc"));
        assert!(glob_match("a*b*c", "axxxbxxxc"));
        assert!(!glob_match("a*b*c", "xxxbxxx"));
    }
}