use std::str::FromStr;

use camino::Utf8PathBuf;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::module_name;

/// Represents a fully qualified function name including its module path.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct QualifiedFunctionName {
    function_name: String,
    module_path: ModulePath,
}

impl QualifiedFunctionName {
    /// Create a new qualified function name.
    pub const fn new(function_name: String, module_path: ModulePath) -> Self {
        Self {
            function_name,
            module_path,
        }
    }

    /// Return the unqualified function name.
    pub fn function_name(&self) -> &str {
        &self.function_name
    }

    /// Return the module path this function belongs to.
    pub const fn module_path(&self) -> &ModulePath {
        &self.module_path
    }
}

impl std::fmt::Display for QualifiedFunctionName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}::{}",
            self.module_path.module_name(),
            self.function_name
        )
    }
}

impl Serialize for QualifiedFunctionName {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for QualifiedFunctionName {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(serde::de::Error::custom)
    }
}

impl FromStr for QualifiedFunctionName {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Split on the last "::" to separate module_path from function_name
        let (module_str, function_name) = s
            .rsplit_once("::")
            .ok_or_else(|| "Invalid qualified function name format".to_string())?;

        let module_path = module_str.parse()?;

        Ok(Self::new(function_name.to_string(), module_path))
    }
}

/// Represents a fully qualified test name, optionally including a parametrized variant.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct QualifiedTestName {
    function_name: QualifiedFunctionName,
    full_name: Option<String>,
}

impl QualifiedTestName {
    /// Create a new qualified test name.
    pub const fn new(function_name: QualifiedFunctionName, full_name: Option<String>) -> Self {
        Self {
            function_name,
            full_name,
        }
    }

    /// Return the underlying qualified function name.
    pub const fn function_name(&self) -> &QualifiedFunctionName {
        &self.function_name
    }

    /// Return the full parametrized name, if any (e.g., `"test_add[1-2]"`).
    pub fn full_name(&self) -> Option<&str> {
        self.full_name.as_deref()
    }
}

impl std::fmt::Display for QualifiedTestName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(full_name) = &self.full_name {
            write!(f, "{full_name}")
        } else {
            write!(f, "{}", self.function_name)
        }
    }
}

/// A Python module path combining the filesystem path with its dotted module name.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ModulePath {
    path: Utf8PathBuf,
    module_name: String,
}

impl ModulePath {
    /// Create a new module path by computing the dotted module name relative to `cwd`.
    pub fn new<P: Into<Utf8PathBuf>>(path: P, cwd: &Utf8PathBuf) -> Option<Self> {
        let path = path.into();
        let module_name = module_name(cwd, path.as_ref())?;
        Some(Self { path, module_name })
    }

    /// Return the dotted module name (e.g., `"tests.test_add"`).
    pub fn module_name(&self) -> &str {
        self.module_name.as_str()
    }

    /// Return the filesystem path of this module.
    pub const fn path(&self) -> &Utf8PathBuf {
        &self.path
    }
}

impl Serialize for ModulePath {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.module_name)
    }
}

impl<'de> Deserialize<'de> for ModulePath {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(serde::de::Error::custom)
    }
}

impl FromStr for ModulePath {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Convert module name to path (e.g., "foo.bar.baz" -> "foo/bar/baz.py")
        let path = s.replace('.', "/") + ".py";

        Ok(Self {
            path: path.into(),
            module_name: s.to_string(),
        })
    }
}

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

    #[test]
    fn test_module_path_from_str() {
        let path: ModulePath = "foo.bar.baz".parse().expect("valid module path");
        assert_eq!(path.module_name(), "foo.bar.baz");
        assert_eq!(path.path().as_str(), "foo/bar/baz.py");
    }

    #[test]
    fn test_qualified_function_name_from_str() {
        let name: QualifiedFunctionName =
            "foo.bar::test_add".parse().expect("valid qualified name");
        assert_eq!(name.function_name(), "test_add");
        assert_eq!(name.module_path().module_name(), "foo.bar");
    }

    #[test]
    fn test_qualified_function_name_from_str_invalid() {
        let result: Result<QualifiedFunctionName, _> = "foo.bar.test_add".parse();
        assert!(result.is_err());
    }

    #[test]
    fn test_qualified_function_name_display() {
        let name: QualifiedFunctionName =
            "foo.bar::test_add".parse().expect("valid qualified name");
        assert_eq!(name.to_string(), "foo.bar::test_add");
    }

    #[test]
    fn test_qualified_test_name_display_with_full_name() {
        let func_name: QualifiedFunctionName =
            "foo.bar::test_add".parse().expect("valid qualified name");
        let test_name = QualifiedTestName::new(func_name, Some("test_add[1-2]".to_string()));
        assert_eq!(test_name.to_string(), "test_add[1-2]");
    }

    #[test]
    fn test_qualified_test_name_display_without_full_name() {
        let func_name: QualifiedFunctionName =
            "foo.bar::test_add".parse().expect("valid qualified name");
        let test_name = QualifiedTestName::new(func_name, None);
        assert_eq!(test_name.to_string(), "foo.bar::test_add");
    }
}
