use serde::{Deserialize, Serialize};

/// Validate that a slug (org name, project name) is safe for use in URLs and templates.
/// Allows lowercase alphanumeric, hyphens, max 64 chars. Must not be empty.
pub fn validate_slug(s: &str) -> bool {
    !s.is_empty()
        && s.len() <= 64
        && s.chars()
            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
        && !s.starts_with('-')
        && !s.ends_with('-')
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Organisation {
    pub organisation_id: String,
    pub name: String,
    pub role: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
    pub artifact_id: String,
    pub slug: String,
    pub context: ArtifactContext,
    #[serde(default)]
    pub source: Option<ArtifactSource>,
    #[serde(default)]
    pub git_ref: Option<ArtifactRef>,
    #[serde(default)]
    pub destinations: Vec<ArtifactDestination>,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactContext {
    pub title: String,
    pub description: Option<String>,
    #[serde(default)]
    pub web: Option<String>,
    #[serde(default)]
    pub pr: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactSource {
    pub user: Option<String>,
    pub email: Option<String>,
    pub source_type: Option<String>,
    pub run_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactRef {
    pub commit_sha: String,
    pub branch: Option<String>,
    pub commit_message: Option<String>,
    pub version: Option<String>,
    pub repo_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArtifactDestination {
    pub name: String,
    pub environment: String,
    #[serde(default)]
    pub type_organisation: Option<String>,
    #[serde(default)]
    pub type_name: Option<String>,
    #[serde(default)]
    pub type_version: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgMember {
    pub user_id: String,
    pub username: String,
    pub role: String,
    pub joined_at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Destination {
    pub name: String,
    pub environment: String,
    pub organisation: String,
    #[serde(default)]
    pub metadata: std::collections::HashMap<String, String>,
    #[serde(default)]
    pub dest_type: Option<DestinationType>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DestinationType {
    pub organisation: String,
    pub name: String,
    pub version: u64,
}

#[derive(Debug, Clone, thiserror::Error)]
pub enum PlatformError {
    #[error("not authenticated")]
    NotAuthenticated,

    #[error("not found: {0}")]
    NotFound(String),

    #[error("service unavailable: {0}")]
    Unavailable(String),

    #[error("{0}")]
    Other(String),
}

/// Trait for platform data from forest-server (organisations, projects, artifacts).
/// Separate from `ForestAuth` which handles identity.
#[async_trait::async_trait]
pub trait ForestPlatform: Send + Sync {
    async fn list_my_organisations(
        &self,
        access_token: &str,
    ) -> Result<Vec<Organisation>, PlatformError>;

    async fn list_projects(
        &self,
        access_token: &str,
        organisation: &str,
    ) -> Result<Vec<String>, PlatformError>;

    async fn list_artifacts(
        &self,
        access_token: &str,
        organisation: &str,
        project: &str,
    ) -> Result<Vec<Artifact>, PlatformError>;

    async fn create_organisation(
        &self,
        access_token: &str,
        name: &str,
    ) -> Result<String, PlatformError>;

    async fn list_members(
        &self,
        access_token: &str,
        organisation_id: &str,
    ) -> Result<Vec<OrgMember>, PlatformError>;

    async fn add_member(
        &self,
        access_token: &str,
        organisation_id: &str,
        user_id: &str,
        role: &str,
    ) -> Result<OrgMember, PlatformError>;

    async fn remove_member(
        &self,
        access_token: &str,
        organisation_id: &str,
        user_id: &str,
    ) -> Result<(), PlatformError>;

    async fn update_member_role(
        &self,
        access_token: &str,
        organisation_id: &str,
        user_id: &str,
        role: &str,
    ) -> Result<OrgMember, PlatformError>;

    async fn get_artifact_by_slug(
        &self,
        access_token: &str,
        slug: &str,
    ) -> Result<Artifact, PlatformError>;

    async fn list_destinations(
        &self,
        access_token: &str,
        organisation: &str,
    ) -> Result<Vec<Destination>, PlatformError>;
}

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

    #[test]
    fn valid_slugs() {
        assert!(validate_slug("my-org"));
        assert!(validate_slug("a"));
        assert!(validate_slug("abc123"));
        assert!(validate_slug("my-cool-project-2"));
    }

    #[test]
    fn invalid_slugs() {
        assert!(!validate_slug(""));
        assert!(!validate_slug("-starts-with-dash"));
        assert!(!validate_slug("ends-with-dash-"));
        assert!(!validate_slug("UPPERCASE"));
        assert!(!validate_slug("has spaces"));
        assert!(!validate_slug("has_underscores"));
        assert!(!validate_slug("has.dots"));
        assert!(!validate_slug(&"a".repeat(65)));
    }

    #[test]
    fn max_length_slug_is_valid() {
        assert!(validate_slug(&"a".repeat(64)));
    }
}
