use std::collections::BTreeMap;

use k8s_openapi::api::core::v1::{Service, ServicePort, ServiceSpec};
use kube::api::ObjectMeta;
use kube::Resource;

use crate::types::SojuBouncer;

pub fn build_service(bouncer: &SojuBouncer) -> Service {
    let name = bouncer.metadata.name.clone().unwrap();
    let ns = bouncer.metadata.namespace.clone().unwrap();
    let oref = bouncer.controller_owner_ref(&()).unwrap();

    let selector: BTreeMap<String, String> =
        [("app.kubernetes.io/name".to_string(), name.clone())].into();

    let mut ports: Vec<ServicePort> = bouncer
        .spec
        .listeners
        .iter()
        .enumerate()
        .filter_map(|(i, l)| {
            l.address
                .rsplit(':')
                .next()
                .and_then(|p| p.parse::<i32>().ok())
                .map(|port| ServicePort {
                    name: Some(format!("listener-{i}")),
                    port,
                    ..Default::default()
                })
        })
        .collect();

    if name != "soju-shared" {
        ports.push(ServicePort {
            name: Some("http".to_string()),
            port: 8081,
            ..Default::default()
        });
    }

    Service {
        metadata: ObjectMeta {
            name: Some(name),
            namespace: Some(ns),
            owner_references: Some(vec![oref]),
            ..Default::default()
        },
        spec: Some(ServiceSpec {
            selector: Some(selector),
            ports: if ports.is_empty() { None } else { Some(ports) },
            ..Default::default()
        }),
        ..Default::default()
    }
}

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

    #[test]
    fn service_named_correctly() {
        let svc = build_service(&test_bouncer());
        assert_eq!(svc.metadata.name.unwrap(), "test-bouncer");
    }

    #[test]
    fn service_has_correct_selector() {
        let svc = build_service(&test_bouncer());
        let selector = svc.spec.unwrap().selector.unwrap();
        assert_eq!(selector["app.kubernetes.io/name"], "test-bouncer");
    }

    #[test]
    fn service_has_ports_for_each_listener() {
        let svc = build_service(&test_bouncer());
        let ports = svc.spec.unwrap().ports.unwrap();
        assert_eq!(ports.len(), 3);
        assert_eq!(ports[0].port, 6697);
        assert_eq!(ports[1].port, 6667);
        assert_eq!(ports[2].port, 8081);
        assert_eq!(ports[2].name.as_deref(), Some("http"));
    }

    #[test]
    fn shared_bouncer_has_no_http_port() {
        let mut b = test_bouncer();
        b.metadata.name = Some("soju-shared".to_string());
        let svc = build_service(&b);
        let ports = svc.spec.unwrap().ports.unwrap();
        assert_eq!(ports.len(), 2);
        assert!(ports.iter().all(|p| p.name.as_deref() != Some("http")));
    }

    #[test]
    fn service_with_no_listeners_shared_has_no_ports() {
        let mut b = test_bouncer();
        b.metadata.name = Some("soju-shared".to_string());
        b.spec.listeners.clear();
        let svc = build_service(&b);
        assert!(svc.spec.unwrap().ports.is_none());
    }

    #[test]
    fn service_has_owner_reference() {
        let svc = build_service(&test_bouncer());
        let orefs = svc.metadata.owner_references.unwrap();
        assert_eq!(orefs.len(), 1);
        assert_eq!(orefs[0].name, "test-bouncer");
    }
}
