use std::collections::HashMap;

#[derive(Clone, Debug)]
pub enum Deployment {
    Wasm,
    Forte { frontend_script_path: String },
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CodeKind {
    Wasm,
    Js,
}

#[derive(Default)]
pub struct DeploymentMap {
    deployments: HashMap<String, Deployment>,
    artifacts: HashMap<String, CodeKind>,
}

impl DeploymentMap {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn register_deployment(&mut self, code_id: &str, deployment: Deployment) {
        match &deployment {
            Deployment::Wasm => {
                self.artifacts
                    .insert(format!("{code_id}::backend"), CodeKind::Wasm);
            }
            Deployment::Forte { .. } => {
                self.artifacts
                    .insert(format!("{code_id}::backend"), CodeKind::Wasm);
                self.artifacts
                    .insert(format!("{code_id}::frontend"), CodeKind::Js);
            }
        }
        self.deployments.insert(code_id.to_string(), deployment);
    }

    pub fn unregister_deployment(&mut self, code_id: &str) {
        if let Some(deployment) = self.deployments.remove(code_id) {
            match deployment {
                Deployment::Wasm => {
                    self.artifacts.remove(&format!("{code_id}::backend"));
                }
                Deployment::Forte { .. } => {
                    self.artifacts.remove(&format!("{code_id}::backend"));
                    self.artifacts.remove(&format!("{code_id}::frontend"));
                }
            }
        }
    }

    pub fn deployment(&self, code_id: &str) -> Option<Deployment> {
        self.deployments.get(code_id).cloned()
    }

    #[allow(dead_code)]
    pub(crate) fn artifact_kind(&self, artifact_id: &str) -> Option<CodeKind> {
        self.artifacts.get(artifact_id).copied()
    }
}

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

    #[test]
    fn register_and_query_wasm() {
        let mut map = DeploymentMap::new();
        map.register_deployment("app-1", Deployment::Wasm);

        assert!(matches!(map.deployment("app-1"), Some(Deployment::Wasm)));
        assert_eq!(map.artifact_kind("app-1::backend"), Some(CodeKind::Wasm));
    }

    #[test]
    fn register_and_query_forte() {
        let mut map = DeploymentMap::new();
        map.register_deployment(
            "app-1",
            Deployment::Forte {
                frontend_script_path: "/server.js".to_string(),
            },
        );

        match map.deployment("app-1") {
            Some(Deployment::Forte { frontend_script_path }) => {
                assert_eq!(frontend_script_path, "/server.js");
            }
            other => panic!("expected Forte, got {other:?}"),
        }
        assert_eq!(map.artifact_kind("app-1::backend"), Some(CodeKind::Wasm));
        assert_eq!(map.artifact_kind("app-1::frontend"), Some(CodeKind::Js));
    }

    #[test]
    fn unregister_removes_all_artifacts() {
        let mut map = DeploymentMap::new();
        map.register_deployment(
            "app-1",
            Deployment::Forte {
                frontend_script_path: "/s.js".to_string(),
            },
        );
        map.unregister_deployment("app-1");

        assert!(map.deployment("app-1").is_none());
        assert!(map.artifact_kind("app-1::backend").is_none());
        assert!(map.artifact_kind("app-1::frontend").is_none());
    }

    #[test]
    fn re_register_switches_kind() {
        let mut map = DeploymentMap::new();
        map.register_deployment("app-1", Deployment::Wasm);
        map.unregister_deployment("app-1");
        map.register_deployment(
            "app-1",
            Deployment::Forte {
                frontend_script_path: "/s.js".to_string(),
            },
        );

        assert!(matches!(
            map.deployment("app-1"),
            Some(Deployment::Forte { .. })
        ));
        assert_eq!(map.artifact_kind("app-1::backend"), Some(CodeKind::Wasm));
        assert_eq!(map.artifact_kind("app-1::frontend"), Some(CodeKind::Js));
    }
}
