use k8s_openapi::api::core::v1::ConfigMap;
use kube::api::ObjectMeta;
use kube::Resource;

use crate::types::ErgoNetwork;

pub fn build_configmap(network: &ErgoNetwork, oper_hash: Option<&str>, oauth2_secret: Option<&str>) -> ConfigMap {
    let name = network.metadata.name.clone().unwrap();
    let ns = network.metadata.namespace.clone().unwrap();
    let oref = network.controller_owner_ref(&()).unwrap();

    let config = generate_ergo_config(network, oper_hash, oauth2_secret);

    ConfigMap {
        metadata: ObjectMeta {
            name: Some(format!("{name}-config")),
            namespace: Some(ns),
            owner_references: Some(vec![oref]),
            ..Default::default()
        },
        data: Some([("ircd.yaml".to_string(), config)].into()),
        ..Default::default()
    }
}

fn generate_ergo_config(network: &ErgoNetwork, oper_hash: Option<&str>, oauth2_secret: Option<&str>) -> String {
    let spec = &network.spec;
    let network_name = spec
        .network_name
        .as_deref()
        .unwrap_or(&spec.hostname);

    let mut lines = Vec::new();

    lines.push(format!("server:\n    name: {}", spec.hostname));

    lines.push("    listeners:".to_string());
    for listener in &spec.listeners {
        if listener.tls {
            lines.push(format!(
                "        \"{}\":\n            tls:\n                cert: /config/tls/tls.crt\n                key: /config/tls/tls.key",
                listener.address
            ));
        } else {
            lines.push(format!("        \"{}\":", listener.address));
        }
    }

    lines.push("    max-sendq: 96k".to_string());

    if let Some(cloaking) = &spec.cloaking {
        lines.push(format!(
            "    ip-cloaking:\n        enabled: true\n        enabled-for-always-on: true\n        netname: \"{}\"\n        cidr-len-ipv4: 32\n        cidr-len-ipv6: 64\n        num-bits: 64",
            cloaking.netname
        ));
    }

    lines.push(format!("\nnetwork:\n    name: \"{}\"", network_name));

    lines.push("\ndatastore:\n    path: /ircd/ircd.db\n    autoupgrade: true".to_string());

    lines.push("\nlimits:\n    nicklen: 32\n    identlen: 20\n    realnamelen: 150\n    channellen: 64\n    awaylen: 390\n    kicklen: 390\n    topiclen: 390\n    monitor-entries: 100\n    whowas-entries: 100\n    chan-list-modes: 100\n    registration-messages: 1024\n    multiline:\n        max-bytes: 4096\n        max-lines: 100".to_string());

    if let Some(hash) = oper_hash {
        lines.push(format!(
            "\nopers:\n    admin:\n        class: server-admin\n        password: \"{}\"",
            hash
        ));
    }

    lines.push("\naccounts:\n    vhosts:\n        enabled: true\n        max-length: 64\n        valid-regexp: '^[a-zA-Z0-9.\\-]+$'".to_string());

    if let Some(oauth2) = &spec.oauth2 {
        if let Some(secret) = oauth2_secret {
            lines.push(format!(
                "    oauth2:\n        enabled: true\n        autocreate: {}\n        introspection-url: \"{}\"\n        introspection-timeout: 10s\n        client-id: ergo\n        client-secret: \"{}\"",
                oauth2.autocreate, oauth2.introspection_url, secret
            ));
        }
    }

    lines.push(String::new());
    lines.join("\n")
}

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

    #[test]
    fn config_contains_hostname() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("test-network.irc.now"));
    }

    #[test]
    fn config_contains_network_name() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("Test Network"));
    }

    #[test]
    fn config_contains_listeners() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains(":6697"));
        assert!(config.contains(":6667"));
    }

    #[test]
    fn config_contains_tls_paths() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("/config/tls/tls.crt"));
        assert!(config.contains("/config/tls/tls.key"));
    }

    #[test]
    fn config_contains_oper_hash() {
        let hash = "$2a$10$examplehash";
        let cm = build_configmap(&test_network(), Some(hash), None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains(hash));
        assert!(config.contains("server-admin"));
    }

    #[test]
    fn config_without_oper_omits_opers_section() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(!config.contains("opers"));
    }

    #[test]
    fn network_name_defaults_to_hostname() {
        let mut network = test_network();
        network.spec.network_name = None;
        let cm = build_configmap(&network, None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("test-network.irc.now"));
    }

    #[test]
    fn configmap_named_correctly() {
        let cm = build_configmap(&test_network(), None, None);
        assert_eq!(cm.metadata.name.unwrap(), "test-network-config");
    }

    #[test]
    fn configmap_has_owner_reference() {
        let cm = build_configmap(&test_network(), None, None);
        let orefs = cm.metadata.owner_references.unwrap();
        assert_eq!(orefs.len(), 1);
        assert_eq!(orefs[0].name, "test-network");
    }

    #[test]
    fn config_contains_oauth2_when_configured() {
        let cm = build_configmap(&test_network(), None, Some("test-secret"));
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("oauth2"));
        assert!(config.contains("introspection"));
        assert!(config.contains("autocreate: true"));
    }

    #[test]
    fn config_omits_oauth2_when_not_configured() {
        let mut network = test_network();
        network.spec.oauth2 = None;
        let cm = build_configmap(&network, None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(!config.contains("oauth2"));
    }

    #[test]
    fn config_omits_oauth2_without_secret() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(!config.contains("oauth2"));
    }

    #[test]
    fn config_contains_cloaking_when_configured() {
        let cm = build_configmap(&test_network(), None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(config.contains("ip-cloaking"));
        assert!(config.contains("irc.now"));
    }

    #[test]
    fn config_omits_cloaking_when_not_configured() {
        let mut network = test_network();
        network.spec.cloaking = None;
        let cm = build_configmap(&network, None, None);
        let data = cm.data.unwrap();
        let config = &data["ircd.yaml"];
        assert!(!config.contains("ip-cloaking"));
    }
}
