use crate::{
    invocation::{SkillContext, SkillInput},
    outcome::SkillOutcome,
    skill::{Skill, SkillId},
};
use serde_json::json;

/// Crossroads: extract API route definitions from a file or directory.
/// Supports Express, Axum, FastAPI, Django, Go net/http, and Rails patterns.
pub struct CrossroadsSkill;

impl Skill for CrossroadsSkill {
    fn id(&self) -> SkillId {
        SkillId("crossroads")
    }
    fn display_name(&self) -> &str {
        "Crossroads"
    }

    fn input_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "path": { "type": "string", "description": "File or directory to scan" },
                "content": { "type": "string", "description": "Content to scan (alternative to path)" },
                "framework": {
                    "type": "string",
                    "enum": ["auto", "express", "axum", "fastapi", "django", "rails", "go"],
                    "default": "auto"
                }
            }
        })
    }

    fn output_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "routes": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "method": { "type": "string" },
                            "path": { "type": "string" },
                            "handler": { "type": "string" },
                            "line": { "type": "integer" }
                        }
                    }
                },
                "count": { "type": "integer" },
                "framework_detected": { "type": "string" }
            }
        })
    }

    fn execute(&self, input: SkillInput, _ctx: &SkillContext) -> anyhow::Result<SkillOutcome> {
        let content = if let Some(c) = input.get_str("content") {
            c.to_string()
        } else if let Some(p) = input.get_str("path") {
            std::fs::read_to_string(p).unwrap_or_default()
        } else {
            return Ok(SkillOutcome::failure(
                "crossroads",
                "path or content required",
            ));
        };

        let (routes, framework) = extract_routes(&content);
        let count = routes.len();

        Ok(SkillOutcome::success(
            "crossroads",
            json!({
                "routes": routes,
                "count": count,
                "framework_detected": framework
            }),
        ))
    }
}

fn extract_routes(content: &str) -> (Vec<serde_json::Value>, &'static str) {
    let mut routes = Vec::new();

    // Detect framework
    let framework = if content.contains("use axum")
        || content.contains(".route(") && content.contains("get(")
    {
        "axum"
    } else if content.contains("from fastapi") || content.contains("@app.") {
        "fastapi"
    } else if content.contains("urlpatterns") {
        "django"
    } else if content.contains("express()")
        || content.contains("router.get")
        || content.contains("app.get(")
    {
        "express"
    } else if content.contains("http.HandleFunc") || content.contains("http.Handle(") {
        "go"
    } else {
        "auto"
    };

    static HTTP_METHODS: &[&str] = &["get", "post", "put", "patch", "delete", "head", "options"];

    for (i, line) in content.lines().enumerate() {
        let t = line.trim();

        // Express: app.get('/path', handler) or router.post('/path', handler)
        if t.starts_with("app.") || t.starts_with("router.") {
            for method in HTTP_METHODS {
                let pat = format!(".{method}(");
                if let Some(rest) = t.find(&pat).map(|p| &t[p + pat.len()..]) {
                    if let Some(path) = extract_quoted(rest) {
                        routes.push(json!({
                            "method": method.to_uppercase(),
                            "path": path,
                            "handler": t.to_string(),
                            "line": i + 1
                        }));
                    }
                }
            }
        }

        // FastAPI: @app.get("/path") or @router.post("/path")
        if t.starts_with("@app.") || t.starts_with("@router.") {
            for method in HTTP_METHODS {
                let pat = format!(".{method}(");
                if let Some(rest) = t.find(&pat).map(|p| &t[p + pat.len()..]) {
                    if let Some(path) = extract_quoted(rest) {
                        routes.push(json!({
                            "method": method.to_uppercase(),
                            "path": path,
                            "handler": t.to_string(),
                            "line": i + 1
                        }));
                    }
                }
            }
        }

        // Axum: .route("/path", get(handler))
        if t.contains(".route(") {
            if let Some(path) = extract_quoted(&t[t.find(".route(").unwrap() + 7..]) {
                let method = HTTP_METHODS
                    .iter()
                    .find(|&&m| t.contains(&format!("{m}(")))
                    .map(|m| m.to_uppercase())
                    .unwrap_or_else(|| "ANY".to_string());
                routes.push(json!({
                    "method": method,
                    "path": path,
                    "handler": t.to_string(),
                    "line": i + 1
                }));
            }
        }

        // Go: http.HandleFunc("/path", handler)
        if t.contains("HandleFunc(") || t.contains("Handle(") {
            if let Some(path) = extract_quoted(t) {
                routes.push(json!({
                    "method": "ANY",
                    "path": path,
                    "handler": t.to_string(),
                    "line": i + 1
                }));
            }
        }
    }

    (routes, framework)
}

fn extract_quoted(s: &str) -> Option<String> {
    let s = s.trim();
    let close = if s.starts_with('"') {
        '"'
    } else if s.starts_with('\'') {
        '\''
    } else {
        return None;
    };
    let inner = &s[1..];
    let end = inner.find(close)?;
    Some(inner[..end].to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extract_quoted_double() {
        assert_eq!(extract_quoted(r#""/api/users""#), Some("/api/users".into()));
    }

    #[test]
    fn extract_quoted_single() {
        assert_eq!(extract_quoted("'/health'"), Some("/health".into()));
    }

    #[test]
    fn extract_quoted_no_quote_returns_none() {
        assert_eq!(extract_quoted("handler"), None);
    }

    #[test]
    fn express_get_route_detected() {
        let src = "app.get('/users', getUsers);\napp.post('/users', createUser);";
        let (routes, _) = extract_routes(src);
        assert_eq!(routes.len(), 2);
        assert_eq!(routes[0]["method"], "GET");
        assert_eq!(routes[0]["path"], "/users");
        assert_eq!(routes[1]["method"], "POST");
    }

    #[test]
    fn fastapi_decorator_detected() {
        let src = "@app.get(\"/items\")\ndef read_items():\n    pass";
        let (routes, fw) = extract_routes(src);
        assert_eq!(fw, "fastapi");
        assert_eq!(routes.len(), 1);
        assert_eq!(routes[0]["path"], "/items");
    }

    #[test]
    fn axum_route_detected() {
        let src = "use axum;\n.route(\"/ping\", get(ping_handler))";
        let (routes, fw) = extract_routes(src);
        assert_eq!(fw, "axum");
        assert!(!routes.is_empty());
        assert_eq!(routes[0]["path"], "/ping");
    }

    #[test]
    fn no_routes_in_plain_file() {
        let src = "let x = 1;\nprintln!(\"hello\");";
        let (routes, _) = extract_routes(src);
        assert!(routes.is_empty());
    }
}
