use axum::http::StatusCode;
use uuid::Uuid;

use crate::db::Database;
use crate::error::{db_error_status, AppError};
use crate::types::{Edge, Node, Project};

pub(crate) fn parse_uuid(s: &str) -> Result<uuid::Uuid, StatusCode> {
    Uuid::parse_str(s).map_err(|_| {
        let err = AppError::bad_request(
            "request.invalid_uuid",
            "parse_uuid",
            "The provided identifier is not a valid UUID.",
            "提供的标识符不是有效的 UUID。",
        )
        .with_field("id")
        .with_hint(format!("Received value: '{s}'"));
        let status = err.status;
        err.log();
        status
    })
}

/// Resolve a project ID — accepts full UUID or a prefix (≥6 chars).
pub(crate) async fn resolve_project_id(db: &Database, raw: &str) -> Result<Uuid, StatusCode> {
    // Try full UUID first
    if let Ok(id) = Uuid::parse_str(raw) {
        return Ok(id);
    }
    // Short prefix matching
    if raw.len() < 6 {
        let err = AppError::bad_request(
            "project_id.too_short",
            "resolve_project_id",
            "Project ID prefixes must be at least 6 characters long.",
            "项目 ID 前缀至少需要 6 个字符。",
        )
        .with_entity("project")
        .with_field("project_id");
        err.log();
        return Err(err.status);
    }
    let projects = db
        .list_projects()
        .map_err(|err| db_error_status("resolve_project_id.list_projects", "project", err))?;
    let matches: Vec<&Project> = projects
        .iter()
        .filter(|p| p.id.to_string().starts_with(raw))
        .collect();
    match matches.len() {
        0 => {
            let err = AppError::not_found(
                "project.not_found",
                "resolve_project_id",
                "No project matches the provided ID or prefix.",
                "没有项目匹配提供的 ID 或前缀。",
            )
            .with_entity("project")
            .with_field("project_id")
            .with_hint(format!("Prefix: '{raw}'"));
            err.log();
            Err(err.status)
        }
        1 => Ok(matches[0].id),
        _ => {
            let err = AppError::bad_request(
                "project_id.ambiguous",
                "resolve_project_id",
                "The project ID prefix is ambiguous.",
                "项目 ID 前缀不唯一。",
            )
            .with_entity("project")
            .with_field("project_id")
            .with_hint(format!(
                "Prefix '{raw}' matched {} projects.",
                matches.len()
            ));
            err.log();
            Err(err.status)
        }
    }
}

/// Resolve an edge ID within a project — accepts full UUID or a prefix (≥6 chars).
pub(crate) fn resolve_edge_id(
    db: &Database,
    project_id: Uuid,
    raw: &str,
) -> Result<uuid::Uuid, StatusCode> {
    // Try full UUID first
    if let Ok(id) = Uuid::parse_str(raw) {
        return Ok(id);
    }
    // Short prefix matching
    if raw.len() < 6 {
        let err = AppError::bad_request(
            "edge_id.too_short",
            "resolve_edge_id",
            "Edge ID prefixes must be at least 6 characters long.",
            "边 ID 前缀至少需要 6 个字符。",
        )
        .with_entity("edge")
        .with_field("edge_id");
        err.log();
        return Err(err.status);
    }
    let edges = db
        .list_edges(project_id)
        .map_err(|err| db_error_status("resolve_edge_id.list_edges", "edge", err))?;
    let matches: Vec<&Edge> = edges
        .iter()
        .filter(|e| e.id.to_string().starts_with(raw))
        .collect();
    match matches.len() {
        0 => {
            let err = AppError::not_found(
                "edge.not_found",
                "resolve_edge_id",
                "No edge matches the provided ID or prefix.",
                "没有边匹配提供的 ID 或前缀。",
            )
            .with_entity("edge")
            .with_field("edge_id")
            .with_hint(format!("Prefix: '{raw}'"));
            err.log();
            Err(err.status)
        }
        1 => Ok(matches[0].id),
        _ => {
            let err = AppError::bad_request(
                "edge_id.ambiguous",
                "resolve_edge_id",
                "The edge ID prefix is ambiguous.",
                "边 ID 前缀不唯一。",
            )
            .with_entity("edge")
            .with_field("edge_id")
            .with_hint(format!("Prefix '{raw}' matched {} edges.", matches.len()));
            err.log();
            Err(err.status)
        }
    }
}

/// Resolve a node ID within a project — accepts full UUID or a prefix (≥6 chars).
pub(crate) fn resolve_node_id(
    db: &Database,
    project_id: Uuid,
    raw: &str,
) -> Result<uuid::Uuid, StatusCode> {
    // Try full UUID first
    if let Ok(id) = Uuid::parse_str(raw) {
        return Ok(id);
    }
    // Short prefix matching
    if raw.len() < 6 {
        let err = AppError::bad_request(
            "node_id.too_short",
            "resolve_node_id",
            "Node ID prefixes must be at least 6 characters long.",
            "节点 ID 前缀至少需要 6 个字符。",
        )
        .with_entity("node")
        .with_field("node_id");
        err.log();
        return Err(err.status);
    }
    let nodes = db
        .list_nodes(project_id)
        .map_err(|err| db_error_status("resolve_node_id.list_nodes", "node", err))?;
    let matches: Vec<&Node> = nodes
        .iter()
        .filter(|n| n.id.to_string().starts_with(raw))
        .collect();
    match matches.len() {
        0 => {
            let err = AppError::not_found(
                "node.not_found",
                "resolve_node_id",
                "No node matches the provided ID or prefix.",
                "没有节点匹配提供的 ID 或前缀。",
            )
            .with_entity("node")
            .with_field("node_id")
            .with_hint(format!("Prefix: '{raw}'"));
            err.log();
            Err(err.status)
        }
        1 => Ok(matches[0].id),
        _ => {
            let err = AppError::bad_request(
                "node_id.ambiguous",
                "resolve_node_id",
                "The node ID prefix is ambiguous.",
                "节点 ID 前缀不唯一。",
            )
            .with_entity("node")
            .with_field("node_id")
            .with_hint(format!("Prefix '{raw}' matched {} nodes.", matches.len()));
            err.log();
            Err(err.status)
        }
    }
}
