use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

pub const RUNX_REGISTRY_SOURCE_AUTHORITY_ENV: &str = "RUNX_REGISTRY_SOURCE_AUTHORITY";

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RegistryManifestSourceAuthority {
    OfficialRunx,
    RegistrySource(String),
}

pub fn registry_manifest_source_authority_from_env(
    env: &BTreeMap<String, String>,
) -> Option<RegistryManifestSourceAuthority> {
    if env
        .get(RUNX_REGISTRY_SOURCE_AUTHORITY_ENV)
        .map(String::as_str)
        .map(str::trim)
        .is_some_and(|value| value == "official_runx")
    {
        return Some(RegistryManifestSourceAuthority::OfficialRunx);
    }
    if let Some(registry_url) = env.get("RUNX_REGISTRY_URL") {
        return Some(registry_manifest_source_authority_from_registry_url(
            registry_url,
        ));
    }
    env.get("RUNX_REGISTRY_DIR")
        .map(|value| registry_manifest_source_authority_from_registry_dir(value))
}

pub fn registry_manifest_source_authority_from_registry_url(
    value: &str,
) -> RegistryManifestSourceAuthority {
    if is_official_runx_registry_url(value) {
        RegistryManifestSourceAuthority::OfficialRunx
    } else {
        RegistryManifestSourceAuthority::RegistrySource(format!(
            "remote:{}",
            canonical_registry_url(value)
        ))
    }
}

pub fn registry_manifest_source_authority_from_registry_dir(
    value: &str,
) -> RegistryManifestSourceAuthority {
    let path = Path::new(value);
    let canonical = fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
    RegistryManifestSourceAuthority::RegistrySource(format!("local:{}", canonical.display()))
}

pub fn registry_manifest_source_key(source: &RegistryManifestSourceAuthority) -> String {
    match source {
        RegistryManifestSourceAuthority::OfficialRunx => "official_runx".to_owned(),
        RegistryManifestSourceAuthority::RegistrySource(value) => value.clone(),
    }
}

pub fn is_official_runx_registry_url(value: &str) -> bool {
    matches!(
        canonical_registry_url(value).as_str(),
        "https://runx.ai" | "https://api.runx.ai"
    )
}

fn canonical_registry_url(value: &str) -> String {
    let without_fragment = value.split_once('#').map_or(value, |(prefix, _)| prefix);
    let without_query = without_fragment
        .split_once('?')
        .map_or(without_fragment, |(prefix, _)| prefix);
    let Some((scheme, rest)) = without_query.split_once("://") else {
        return without_query.trim_end_matches('/').to_owned();
    };
    let (authority, path) = rest
        .split_once('/')
        .map_or((rest, ""), |(authority, path)| (authority, path));
    let authority = authority
        .rsplit_once('@')
        .map_or(authority, |(_, host)| host);
    if path.is_empty() {
        format!("{scheme}://{authority}")
    } else {
        format!("{scheme}://{authority}/{}", path.trim_end_matches('/'))
    }
}
