use std::collections::BTreeMap;

use kube::api::ObjectMeta;
use kube::Resource;
use serde::{Deserialize, Serialize};

use crate::types::chatservice::ChatService;
use crate::types::webservice::WebService;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcpRoute {
    pub metadata: ObjectMeta,
    pub spec: OcpRouteSpec,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OcpRouteSpec {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub host: Option<String>,
    pub to: RouteTargetReference,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub port: Option<RoutePort>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tls: Option<RouteTlsConfig>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "wildcardPolicy")]
    pub wildcard_policy: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RouteTargetReference {
    pub kind: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub weight: Option<i32>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoutePort {
    #[serde(rename = "targetPort")]
    pub target_port: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RouteTlsConfig {
    pub termination: String,
    #[serde(
        skip_serializing_if = "Option::is_none",
        rename = "insecureEdgeTerminationPolicy"
    )]
    pub insecure_edge_termination_policy: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "externalCertificate")]
    pub external_certificate: Option<ExternalCertificate>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ExternalCertificate {
    pub name: String,
}

fn slug_host(host: &str) -> String {
    host.replace('.', "-")
}

pub fn build_web_route(ws: &WebService) -> OcpRoute {
    let name = ws.metadata.name.clone().unwrap();
    let ns = ws.metadata.namespace.clone().unwrap();
    let oref = ws.controller_owner_ref(&()).unwrap();
    let slug = slug_host(&ws.spec.host);
    let tls_secret = format!("{slug}-tls");

    build_edge_route(&name, &ns, &ws.spec.host, &tls_secret, None, oref)
}

pub fn build_chat_route(cs: &ChatService) -> OcpRoute {
    let name = cs.metadata.name.clone().unwrap();
    let ns = cs.metadata.namespace.clone().unwrap();
    let oref = cs.controller_owner_ref(&()).unwrap();
    let slug = slug_host(&cs.spec.host);
    let tls_secret = format!("{slug}-tls");

    let mut annotations = BTreeMap::new();
    annotations.insert(
        "haproxy.router.openshift.io/timeout".to_string(),
        format!("{}s", cs.spec.websocket_timeout),
    );

    build_edge_route(
        &name,
        &ns,
        &cs.spec.host,
        &tls_secret,
        Some(annotations),
        oref,
    )
}

fn build_edge_route(
    name: &str,
    ns: &str,
    host: &str,
    tls_secret: &str,
    annotations: Option<BTreeMap<String, String>>,
    oref: k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference,
) -> OcpRoute {
    OcpRoute {
        metadata: ObjectMeta {
            name: Some(name.to_string()),
            namespace: Some(ns.to_string()),
            owner_references: Some(vec![oref]),
            annotations: annotations.map(|a| if a.is_empty() { return a } else { a }),
            ..Default::default()
        },
        spec: OcpRouteSpec {
            host: Some(host.to_string()),
            to: RouteTargetReference {
                kind: "Service".to_string(),
                name: name.to_string(),
                weight: Some(100),
            },
            port: Some(RoutePort {
                target_port: "http".to_string(),
            }),
            tls: Some(RouteTlsConfig {
                termination: "edge".to_string(),
                insecure_edge_termination_policy: Some("Redirect".to_string()),
                external_certificate: Some(ExternalCertificate {
                    name: tls_secret.to_string(),
                }),
            }),
            wildcard_policy: Some("None".to_string()),
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testutil::{test_chatservice, test_webservice};

    #[test]
    fn web_route_uses_edge_tls() {
        let route = build_web_route(&test_webservice());
        let tls = route.spec.tls.unwrap();
        assert_eq!(tls.termination, "edge");
    }

    #[test]
    fn web_route_references_tls_secret() {
        let route = build_web_route(&test_webservice());
        let tls = route.spec.tls.unwrap();
        assert_eq!(tls.external_certificate.unwrap().name, "txt-irc-now-tls");
    }

    #[test]
    fn web_route_has_correct_host() {
        let route = build_web_route(&test_webservice());
        assert_eq!(route.spec.host.unwrap(), "txt.irc.now");
    }

    #[test]
    fn web_route_points_at_service() {
        let route = build_web_route(&test_webservice());
        assert_eq!(route.spec.to.kind, "Service");
        assert_eq!(route.spec.to.name, "txt");
    }

    #[test]
    fn web_route_targets_http_port() {
        let route = build_web_route(&test_webservice());
        assert_eq!(route.spec.port.unwrap().target_port, "http");
    }

    #[test]
    fn web_route_has_owner_reference() {
        let route = build_web_route(&test_webservice());
        let orefs = route.metadata.owner_references.unwrap();
        assert_eq!(orefs[0].name, "txt");
    }

    #[test]
    fn chat_route_has_timeout_annotation() {
        let route = build_chat_route(&test_chatservice());
        let annotations = route.metadata.annotations.unwrap();
        assert_eq!(
            annotations["haproxy.router.openshift.io/timeout"],
            "600s"
        );
    }

    #[test]
    fn chat_route_uses_edge_tls() {
        let route = build_chat_route(&test_chatservice());
        let tls = route.spec.tls.unwrap();
        assert_eq!(tls.termination, "edge");
    }
}
