use kube::CustomResource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

pub fn sanitize_bouncer_name(input: &str) -> String {
    input
        .to_lowercase()
        .chars()
        .map(|c| if c.is_alphanumeric() || c == '-' { c } else { '-' })
        .collect::<String>()
        .trim_matches('-')
        .to_string()
}

// Minimal SojuBouncer spec -- just enough for the portal to create CRs.
// The full CRD is defined in the soju-operator crate.
#[derive(CustomResource, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[kube(
    group = "irc.now",
    version = "v1alpha1",
    kind = "SojuBouncer",
    namespaced,
    status = "SojuBouncerStatus"
)]
pub struct SojuBouncerSpec {
    pub hostname: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub owner: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub hostname_cloak: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub listeners: Vec<Listener>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub auth: Option<AuthSpec>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Listener {
    pub address: String,
    #[serde(default)]
    pub tls: bool,
    #[serde(default)]
    pub websocket: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AuthSpec {
    pub oauth2_url: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub secret_ref: Option<String>,
}

pub fn default_bouncer_spec(name: &str, owner: &str, issuer_url: &str) -> SojuBouncerSpec {
    SojuBouncerSpec {
        hostname: format!("{name}.irc.now"),
        owner: Some(owner.to_string()),
        hostname_cloak: Some(format!("{name}.irc.now")),
        listeners: vec![
            Listener { address: ":6667".to_string(), tls: false, websocket: false },
            Listener { address: ":8080".to_string(), tls: false, websocket: true },
        ],
        auth: Some(AuthSpec {
            oauth2_url: issuer_url.to_string(),
            secret_ref: Some("oidc-soju".to_string()),
        }),
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct SojuBouncerStatus {
    #[serde(default)]
    pub conditions: Vec<SojuBouncerCondition>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SojuBouncerCondition {
    #[serde(rename = "type")]
    pub type_: String,
    pub status: String,
    #[serde(default)]
    pub reason: Option<String>,
    #[serde(default)]
    pub message: Option<String>,
}

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

    #[test]
    fn bouncer_name_sanitizes_input() {
        assert_eq!(sanitize_bouncer_name("My Bouncer!"), "my-bouncer");
        assert_eq!(sanitize_bouncer_name("test_123"), "test-123");
    }

    #[test]
    fn bouncer_name_trims_dashes() {
        assert_eq!(sanitize_bouncer_name("--hello--"), "hello");
    }

    #[test]
    fn bouncer_name_handles_empty() {
        assert_eq!(sanitize_bouncer_name(""), "");
    }
}
