use std::collections::HashSet;

use crate::glyph::extract::common::node_name;
use tree_sitter::Node as TsNode;

use super::intent::generic_code_intent_aliases;

#[derive(Debug)]
pub(crate) struct PythonRoute {
    pub(crate) method: String,
    pub(crate) path: String,
}

impl PythonRoute {
    pub(crate) fn method_path(&self) -> String {
        format!("{} {}", self.method, self.path)
    }
}

pub(crate) fn python_routes(
    span_node: TsNode,
    src: &str,
    fastapi_names: &HashSet<String>,
) -> Vec<PythonRoute> {
    if span_node.kind() != "decorated_definition" {
        return Vec::new();
    }
    let mut routes = Vec::new();
    let mut cursor = span_node.walk();
    for child in span_node.children(&mut cursor) {
        if child.kind() != "decorator" {
            continue;
        }
        routes.extend(python_routes_from_decorator(child, src, fastapi_names));
    }
    routes
}

fn python_routes_from_decorator(
    decorator: TsNode,
    src: &str,
    fastapi_names: &HashSet<String>,
) -> Vec<PythonRoute> {
    let Some(call) = find_first_kind(decorator, "call") else {
        return Vec::new();
    };
    let Some(function) = call.child_by_field_name("function") else {
        return Vec::new();
    };
    let Some((receiver, route_kind)) = python_route_receiver(function, src) else {
        return Vec::new();
    };
    if !fastapi_names.contains(receiver) {
        return Vec::new();
    }
    let Some(args) = call.child_by_field_name("arguments") else {
        return Vec::new();
    };
    let Some(path) = first_python_string(args, src) else {
        return Vec::new();
    };
    let methods = match route_kind {
        PythonRouteKind::Http(method) => vec![method.to_string()],
        PythonRouteKind::ApiRoute => python_api_route_methods(args, src),
    };
    methods
        .into_iter()
        .map(|method| PythonRoute {
            method,
            path: path.clone(),
        })
        .collect()
}

enum PythonRouteKind {
    Http(&'static str),
    ApiRoute,
}

fn python_route_receiver<'a>(function: TsNode, src: &'a str) -> Option<(&'a str, PythonRouteKind)> {
    if function.kind() != "attribute" {
        return None;
    }
    let text = &src[function.byte_range()];
    let (receiver, method) = text.rsplit_once('.')?;
    let kind = match method {
        "get" => Some(PythonRouteKind::Http("GET")),
        "post" => Some(PythonRouteKind::Http("POST")),
        "put" => Some(PythonRouteKind::Http("PUT")),
        "patch" => Some(PythonRouteKind::Http("PATCH")),
        "delete" => Some(PythonRouteKind::Http("DELETE")),
        "options" => Some(PythonRouteKind::Http("OPTIONS")),
        "head" => Some(PythonRouteKind::Http("HEAD")),
        "api_route" => Some(PythonRouteKind::ApiRoute),
        _ => None,
    }?;
    Some((receiver, kind))
}

fn python_api_route_methods(args: TsNode, src: &str) -> Vec<String> {
    let Some(methods_arg) = python_keyword_value(args, src, "methods") else {
        return Vec::new();
    };
    python_strings(methods_arg, src)
        .into_iter()
        .map(|method| method.to_ascii_uppercase())
        .collect()
}

fn python_keyword_value<'tree>(
    node: TsNode<'tree>,
    src: &str,
    name: &str,
) -> Option<TsNode<'tree>> {
    if node.kind() == "keyword_argument" {
        let key = node.child_by_field_name("name")?;
        if key.kind() == "identifier" && &src[key.byte_range()] == name {
            return node.child_by_field_name("value");
        }
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if let Some(found) = python_keyword_value(child, src, name) {
            return Some(found);
        }
    }
    None
}

fn python_strings(node: TsNode, src: &str) -> Vec<String> {
    let mut out = Vec::new();
    collect_python_strings(node, src, &mut out);
    out
}

fn collect_python_strings(node: TsNode, src: &str, out: &mut Vec<String>) {
    if node.kind() == "string" {
        out.push(strip_python_string(&src[node.byte_range()]));
        return;
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_python_strings(child, src, out);
    }
}

fn first_python_string(node: TsNode, src: &str) -> Option<String> {
    if node.kind() == "string" {
        return Some(strip_python_string(&src[node.byte_range()]));
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if let Some(found) = first_python_string(child, src) {
            return Some(found);
        }
    }
    None
}

fn strip_python_string(raw: &str) -> String {
    let mut s = raw.trim();
    while let Some(first) = s.chars().next() {
        if matches!(first, 'r' | 'R' | 'u' | 'U' | 'b' | 'B' | 'f' | 'F') {
            s = &s[first.len_utf8()..];
        } else {
            break;
        }
    }
    for quote in ["'''", "\"\"\"", "'", "\""] {
        if let Some(inner) = s.strip_prefix(quote).and_then(|v| v.strip_suffix(quote)) {
            return inner.to_string();
        }
    }
    s.to_string()
}

fn find_first_kind<'tree>(node: TsNode<'tree>, kind: &str) -> Option<TsNode<'tree>> {
    if node.kind() == kind {
        return Some(node);
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if let Some(found) = find_first_kind(child, kind) {
            return Some(found);
        }
    }
    None
}

pub(crate) fn collect_fastapi_names(root: TsNode, src: &str) -> HashSet<String> {
    let mut names = HashSet::from(["app".to_string(), "router".to_string()]);
    collect_fastapi_names_inner(root, src, &mut names);
    names
}

pub(crate) fn python_class_aliases(class_node: TsNode, src: &str, modpath: &str) -> Vec<String> {
    let mut aliases = vec!["python".to_string(), "class".to_string()];
    let name = node_name(class_node, src).unwrap_or_default();
    let words = name_words(&name);
    if let Some(words) = words.as_deref() {
        aliases.push(format!("{words} class"));
        aliases.push(format!("python {words} class"));
    }
    if is_model_mod(modpath) {
        aliases.push("model".to_string());
        if let Some(words) = words.as_deref() {
            aliases.push(format!("{words} model"));
            aliases.push(format!("python {words} model"));
        }
    }
    if class_extends(class_node, src, "BaseModel") {
        aliases.push("pydantic".to_string());
        aliases.push("pydantic model".to_string());
        aliases.push("model".to_string());
        if let Some(words) = words.as_deref() {
            aliases.push(format!("{words} pydantic model"));
            aliases.push(format!("python {words} pydantic model"));
        }
    }
    aliases.extend(generic_code_intent_aliases(
        &name,
        modpath,
        Some(class_node),
        src,
    ));
    aliases.sort();
    aliases.dedup();
    aliases
}

fn name_words(name: &str) -> Option<String> {
    let mut words = Vec::new();
    let mut current = String::new();
    let mut prev_lower_or_digit = false;
    for ch in name.chars() {
        if ch == '_' || ch == '-' {
            if !current.is_empty() {
                words.push(std::mem::take(&mut current));
            }
            prev_lower_or_digit = false;
            continue;
        }
        if ch.is_uppercase() && prev_lower_or_digit && !current.is_empty() {
            words.push(std::mem::take(&mut current));
        }
        current.push(ch.to_ascii_lowercase());
        prev_lower_or_digit = ch.is_lowercase() || ch.is_ascii_digit();
    }
    if !current.is_empty() {
        words.push(current);
    }
    (words.len() >= 2).then(|| words.join(" "))
}

fn is_model_mod(modpath: &str) -> bool {
    modpath.split("::").any(|part| {
        matches!(part, "model" | "models") || part_has_token(part, &["model", "models"])
    })
}

fn part_has_token(part: &str, wanted: &[&str]) -> bool {
    part.split(['_', '-', '.'])
        .any(|token| wanted.contains(&token))
}

fn class_extends(class_node: TsNode, src: &str, base_name: &str) -> bool {
    let Some(superclasses) = class_node.child_by_field_name("superclasses") else {
        return false;
    };
    contains_name(superclasses, src, base_name)
}

fn contains_name(node: TsNode, src: &str, want: &str) -> bool {
    if node_name(node, src).as_deref() == Some(want) {
        return true;
    }
    if matches!(node.kind(), "identifier" | "type_identifier") && &src[node.byte_range()] == want {
        return true;
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if contains_name(child, src, want) {
            return true;
        }
    }
    false
}

fn collect_fastapi_names_inner(node: TsNode, src: &str, names: &mut HashSet<String>) {
    if node.kind() == "assignment"
        && let (Some(left), Some(right)) = (
            node.child_by_field_name("left"),
            node.child_by_field_name("right"),
        )
        && left.kind() == "identifier"
        && is_fastapi_constructor(right, src)
    {
        names.insert(src[left.byte_range()].to_string());
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_fastapi_names_inner(child, src, names);
    }
}

fn is_fastapi_constructor(node: TsNode, src: &str) -> bool {
    let Some(call) = find_first_kind(node, "call") else {
        return false;
    };
    let Some(function) = call.child_by_field_name("function") else {
        return false;
    };
    let name = src[function.byte_range()]
        .rsplit('.')
        .next()
        .unwrap_or_default();
    matches!(name, "FastAPI" | "APIRouter")
}
