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

#[derive(CustomResource, Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
#[kube(
    group = "irc.josie.cloud",
    version = "v1alpha1",
    kind = "ErgoNetwork",
    namespaced,
    status = "ErgoNetworkStatus",
    shortname = "ergo",
    printcolumn = r#"{"name":"Hostname", "type":"string", "jsonPath":".spec.hostname"}"#,
    printcolumn = r#"{"name":"Network", "type":"string", "jsonPath":".spec.networkName"}"#,
    printcolumn = r#"{"name":"Ready", "type":"string", "jsonPath":".status.conditions[?(@.type==\"Ready\")].status"}"#
)]
pub struct ErgoNetworkSpec {
    pub hostname: String,
    #[serde(default, rename = "networkName")]
    pub network_name: Option<String>,
    #[serde(default, rename = "operCredentials")]
    pub oper_credentials: Option<OperCredentials>,
    #[serde(default)]
    pub listeners: Vec<Listener>,
    #[serde(default)]
    pub tls: Option<TlsSpec>,
    #[serde(default)]
    pub route: Option<RouteSpec>,
    #[serde(default)]
    pub resources: Option<ResourceRequirements>,
    #[serde(default)]
    pub storage: Option<StorageSpec>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct OperCredentials {
    #[serde(rename = "secretName")]
    pub secret_name: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct StorageSpec {
    #[serde(default = "default_storage_size")]
    pub size: String,
    #[serde(default, rename = "storageClassName")]
    pub storage_class_name: Option<String>,
}

fn default_storage_size() -> String {
    "1Gi".to_string()
}

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

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct TlsSpec {
    pub issuer_ref: IssuerRef,
    pub dns_names: Vec<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct IssuerRef {
    pub name: String,
    pub kind: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct RouteSpec {
    pub host: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct ResourceRequirements {
    #[serde(default)]
    pub requests: Option<ResourceList>,
    #[serde(default)]
    pub limits: Option<ResourceList>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct ResourceList {
    #[serde(default)]
    pub memory: Option<String>,
    #[serde(default)]
    pub cpu: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct ErgoNetworkStatus {
    #[serde(default)]
    pub conditions: Vec<Condition>,
    #[serde(default)]
    pub observed_generation: Option<i64>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct Condition {
    #[serde(rename = "type")]
    pub type_: String,
    pub status: String,
    pub reason: String,
    pub message: String,
    pub last_transition_time: String,
}

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

    #[test]
    fn crd_generates_valid_schema() {
        let crd = ErgoNetwork::crd();
        let name = crd.metadata.name.unwrap();
        assert_eq!(name, "ergonetworks.irc.josie.cloud");
    }

    #[test]
    fn default_spec_has_sane_values() {
        let spec = ErgoNetworkSpec::default();
        assert!(spec.listeners.is_empty());
        assert!(spec.tls.is_none());
        assert!(spec.route.is_none());
        assert!(spec.resources.is_none());
        assert!(spec.storage.is_none());
        assert!(spec.oper_credentials.is_none());
        assert!(spec.network_name.is_none());
    }
}
