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>,
    #[serde(default)]
    pub oauth2: Option<OAuth2Spec>,
    #[serde(default)]
    pub cloaking: Option<CloakingSpec>,
    #[serde(default, rename = "maxSendq")]
    pub max_sendq: Option<String>,
    #[serde(default)]
    pub limits: Option<LimitsSpec>,
    #[serde(default)]
    pub vhosts: Option<VhostSpec>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct OperCredentials {
    #[serde(rename = "secretName")]
    pub secret_name: String,
    #[serde(default, rename = "className")]
    pub class_name: Option<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 OAuth2Spec {
    pub introspection_url: String,
    pub secret_ref: String,
    #[serde(default = "default_true")]
    pub autocreate: bool,
    #[serde(default, rename = "clientId")]
    pub client_id: Option<String>,
}

fn default_true() -> bool {
    true
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct CloakingSpec {
    pub netname: String,
    #[serde(default, rename = "cidrLenIpv4")]
    pub cidr_len_ipv4: Option<u32>,
    #[serde(default, rename = "cidrLenIpv6")]
    pub cidr_len_ipv6: Option<u32>,
    #[serde(default, rename = "numBits")]
    pub num_bits: Option<u32>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct LimitsSpec {
    #[serde(default)]
    pub nicklen: Option<u32>,
    #[serde(default)]
    pub identlen: Option<u32>,
    #[serde(default)]
    pub realnamelen: Option<u32>,
    #[serde(default)]
    pub channellen: Option<u32>,
    #[serde(default)]
    pub awaylen: Option<u32>,
    #[serde(default)]
    pub kicklen: Option<u32>,
    #[serde(default)]
    pub topiclen: Option<u32>,
    #[serde(default, rename = "monitorEntries")]
    pub monitor_entries: Option<u32>,
    #[serde(default, rename = "whowasEntries")]
    pub whowas_entries: Option<u32>,
    #[serde(default, rename = "chanListModes")]
    pub chan_list_modes: Option<u32>,
    #[serde(default, rename = "registrationMessages")]
    pub registration_messages: Option<u32>,
    #[serde(default)]
    pub multiline: Option<MultilineSpec>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct MultilineSpec {
    #[serde(default, rename = "maxBytes")]
    pub max_bytes: Option<u32>,
    #[serde(default, rename = "maxLines")]
    pub max_lines: Option<u32>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct VhostSpec {
    #[serde(default)]
    pub enabled: Option<bool>,
    #[serde(default, rename = "maxLength")]
    pub max_length: Option<u32>,
    #[serde(default, rename = "validRegexp")]
    pub valid_regexp: Option<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());
    }

    #[test]
    fn default_spec_has_no_oauth2() {
        let spec = ErgoNetworkSpec::default();
        assert!(spec.oauth2.is_none());
    }

    #[test]
    fn default_spec_has_no_cloaking() {
        let spec = ErgoNetworkSpec::default();
        assert!(spec.cloaking.is_none());
    }
}
