//! Decentralized Identifier (DID) newtype.
//!
//! Provides a validated DID type that ensures format compliance on
//! construction.

use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::error::LastIDError;

/// A validated Decentralized Identifier (DID).
///
/// DIDs follow the `did:method:identifier` format as defined in
/// [W3C DID Core](https://www.w3.org/TR/did-core/).
///
/// This newtype ensures validation on construction, preventing invalid DIDs
/// from propagating through the codebase.
///
/// # Examples
///
/// ```rust
/// use lastid_sdk::types::Did;
///
/// // Valid DIDs
/// let did = Did::new("did:lastid:user123").unwrap();
/// assert_eq!(did.method(), "lastid");
///
/// // Invalid DIDs fail validation
/// assert!(Did::new("not-a-did").is_err());
/// assert!(Did::new("did:").is_err());
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct Did(String);

impl Did {
    /// Creates a new DID after validating the format.
    ///
    /// # Errors
    ///
    /// Returns `LastIDError::InvalidDid` if the string doesn't match the
    /// `did:method:identifier` pattern where:
    /// - Must start with `did:`
    /// - Method must be non-empty alphanumeric/hyphen
    /// - Identifier must be non-empty
    pub fn new(value: impl Into<String>) -> Result<Self, LastIDError> {
        let s = value.into();
        Self::validate(&s)?;
        Ok(Self(s))
    }

    /// Returns the DID as a string slice.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Returns the DID method (e.g., "lastid" from "did:lastid:abc").
    #[must_use]
    pub fn method(&self) -> &str {
        // Safe: validated in constructor
        self.0
            .strip_prefix("did:")
            .and_then(|s| s.split(':').next())
            .unwrap_or("")
    }

    /// Returns the method-specific identifier (everything after `did:method:`).
    #[must_use]
    pub fn identifier(&self) -> &str {
        // Safe: validated in constructor
        let without_prefix = self.0.strip_prefix("did:").unwrap_or("");
        without_prefix
            .find(':')
            .map_or("", |idx| &without_prefix[idx + 1..])
    }

    /// Validates a DID string format.
    fn validate(s: &str) -> Result<(), LastIDError> {
        // Must start with "did:"
        if !s.starts_with("did:") {
            return Err(LastIDError::invalid_did(format!(
                "DID must start with 'did:': {s}"
            )));
        }

        let parts: Vec<&str> = s.splitn(3, ':').collect();

        // Must have at least 3 parts: "did", method, identifier
        if parts.len() < 3 {
            return Err(LastIDError::invalid_did(format!(
                "DID must have format 'did:method:identifier': {s}"
            )));
        }

        let method = parts[1];
        let identifier = parts[2];

        // Method must be non-empty and contain only valid chars
        if method.is_empty() {
            return Err(LastIDError::invalid_did(format!(
                "DID method cannot be empty: {s}"
            )));
        }

        // Method must be lowercase alphanumeric (per W3C DID spec)
        if !method
            .chars()
            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
        {
            return Err(LastIDError::invalid_did(format!(
                "DID method must be lowercase alphanumeric: {s}"
            )));
        }

        // Identifier must be non-empty
        if identifier.is_empty() {
            return Err(LastIDError::invalid_did(format!(
                "DID identifier cannot be empty: {s}"
            )));
        }

        Ok(())
    }
}

impl fmt::Display for Did {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl AsRef<str> for Did {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl From<Did> for String {
    fn from(did: Did) -> Self {
        did.0
    }
}

impl TryFrom<String> for Did {
    type Error = LastIDError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}

impl TryFrom<&str> for Did {
    type Error = LastIDError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}

impl FromStr for Did {
    type Err = LastIDError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::new(s)
    }
}

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

    #[test]
    fn test_valid_did() {
        let did = Did::new("did:lastid:user123").unwrap();
        assert_eq!(did.as_str(), "did:lastid:user123");
        assert_eq!(did.method(), "lastid");
        assert_eq!(did.identifier(), "user123");
    }

    #[test]
    fn test_valid_did_with_colons_in_identifier() {
        let did = Did::new("did:lastid:human.dev.lastid.co").unwrap();
        assert_eq!(did.method(), "lastid");
        assert_eq!(did.identifier(), "human.dev.lastid.co");
    }

    #[test]
    fn test_valid_enterprise_did() {
        let did = Did::new("did:lastid:ent:abc123def456").unwrap();
        assert_eq!(did.method(), "lastid");
        assert_eq!(did.identifier(), "ent:abc123def456");
    }

    #[test]
    fn test_invalid_did_no_prefix() {
        assert!(Did::new("not-a-did").is_err());
    }

    #[test]
    fn test_invalid_did_no_method() {
        assert!(Did::new("did::identifier").is_err());
    }

    #[test]
    fn test_invalid_did_no_identifier() {
        assert!(Did::new("did:method").is_err());
        assert!(Did::new("did:method:").is_err());
    }

    #[test]
    fn test_invalid_did_uppercase_method() {
        assert!(Did::new("did:LastID:user").is_err());
    }

    #[test]
    fn test_display() {
        let did = Did::new("did:web:example.com").unwrap();
        assert_eq!(format!("{did}"), "did:web:example.com");
    }

    #[test]
    fn test_from_str() {
        let did: Did = "did:key:z123".parse().unwrap();
        assert_eq!(did.method(), "key");
    }

    #[test]
    fn test_try_from_string() {
        let did = Did::try_from("did:lastid:test".to_string()).unwrap();
        assert_eq!(did.as_str(), "did:lastid:test");
    }

    #[test]
    fn test_into_string() {
        let did = Did::new("did:lastid:test").unwrap();
        let s: String = did.into();
        assert_eq!(s, "did:lastid:test");
    }

    #[test]
    fn test_serde_roundtrip() {
        let did = Did::new("did:lastid:test123").unwrap();
        let json = serde_json::to_string(&did).unwrap();
        assert_eq!(json, "\"did:lastid:test123\"");

        let deserialized: Did = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, did);
    }

    #[test]
    fn test_serde_invalid_did() {
        let result: Result<Did, _> = serde_json::from_str("\"invalid-did\"");
        assert!(result.is_err());
    }
}
