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

#[derive(CustomResource, Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
#[kube(
    group = "irc.now",
    version = "v1alpha1",
    kind = "WebService",
    namespaced,
    status = "WebServiceStatus",
    shortname = "ws",
    printcolumn = r#"{"name":"Host", "type":"string", "jsonPath":".spec.host"}"#,
    printcolumn = r#"{"name":"Ready", "type":"string", "jsonPath":".status.conditions[?(@.type==\"Ready\")].status"}"#
)]
pub struct WebServiceSpec {
    pub image: String,
    pub host: String,
    #[serde(default)]
    pub tls: Option<TlsSpec>,
    #[serde(default)]
    pub resources: Option<ResourceRequirements>,
    #[serde(default)]
    pub database: Option<DatabaseSpec>,
    #[serde(default)]
    pub oidc: Option<OidcSpec>,
    #[serde(default)]
    pub s3: Option<S3Spec>,
    #[serde(default)]
    pub stripe: Option<StripeSpec>,
    #[serde(default)]
    pub service_account: Option<String>,
    #[serde(default)]
    pub port: Option<i32>,
}

#[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 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 DatabaseSpec {
    pub secret_ref: String,
    pub host: String,
    pub name: String,
    #[serde(default = "default_db_port")]
    pub port: i32,
    #[serde(default)]
    pub env_prefix: Option<String>,
}

fn default_db_port() -> i32 {
    5432
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct OidcSpec {
    pub issuer_url: String,
    pub secret_ref: String,
    pub redirect_url: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct S3Spec {
    pub endpoint: String,
    pub bucket: String,
    pub credentials_secret: String,
}

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

#[derive(Serialize, Deserialize, Default, Debug, Clone, JsonSchema)]
pub struct WebServiceStatus {
    #[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 = WebService::crd();
        let name = crd.metadata.name.unwrap();
        assert_eq!(name, "webservices.irc.now");
    }

    #[test]
    fn default_spec_has_sane_values() {
        let spec = WebServiceSpec::default();
        assert!(spec.database.is_none());
        assert!(spec.oidc.is_none());
        assert!(spec.s3.is_none());
        assert!(spec.stripe.is_none());
        assert!(spec.tls.is_none());
        assert!(spec.resources.is_none());
        assert!(spec.service_account.is_none());
    }

    #[test]
    fn database_spec_defaults_port() {
        let db: DatabaseSpec = serde_json::from_str(
            r#"{"secret_ref":"test","host":"db","name":"test"}"#,
        )
        .unwrap();
        assert_eq!(db.port, 5432);
    }
}
