use std::sync::Arc;

use roto::Runtime;
use serde::{Deserialize, Serialize};

use crate::context::ExecutionContext;
use crate::error::{Error, Result};

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ModuleParameter {
    pub name: String,
    pub doc: String,
    pub secret: bool,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct CapabilityMetadata {
    pub label: String,
}

impl CapabilityMetadata {
    pub fn network() -> Self {
        Self {
            label: String::from("network"),
        }
    }

    pub fn filesystem() -> Self {
        Self {
            label: String::from("filesystem"),
        }
    }

    pub fn time() -> Self {
        Self {
            label: String::from("time"),
        }
    }

    pub fn process() -> Self {
        Self {
            label: String::from("process"),
        }
    }

    pub fn custom(label: impl Into<String>) -> Self {
        Self {
            label: label.into(),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FunctionSpec {
    pub name: String,
    pub doc: String,
    pub args: Vec<String>,
    pub return_type: String,
}

impl FunctionSpec {
    pub fn new(
        module_name: &str,
        function_name: &str,
        doc: impl Into<String>,
        args: Vec<String>,
        return_type: impl Into<String>,
    ) -> Self {
        Self {
            name: qualified_name(module_name, function_name),
            doc: doc.into(),
            args,
            return_type: return_type.into(),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ModuleSpec {
    pub name: String,
    pub doc: String,
    pub capabilities: Vec<CapabilityMetadata>,
    pub parameters: Vec<ModuleParameter>,
    pub functions: Vec<FunctionSpec>,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct FunctionMetadata {
    pub name: String,
    pub doc: String,
    pub args: Vec<String>,
    pub return_type: String,
}

impl From<FunctionSpec> for FunctionMetadata {
    fn from(spec: FunctionSpec) -> Self {
        Self {
            name: spec.name,
            doc: spec.doc,
            args: spec.args,
            return_type: spec.return_type,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ModuleMetadata {
    pub name: String,
    pub doc: String,
    pub capabilities: Vec<String>,
    pub parameters: Vec<ModuleParameter>,
    pub functions: Vec<FunctionMetadata>,
}

impl From<ModuleSpec> for ModuleMetadata {
    fn from(spec: ModuleSpec) -> Self {
        Self {
            name: spec.name,
            doc: spec.doc,
            capabilities: spec
                .capabilities
                .into_iter()
                .map(|c| c.label.clone())
                .collect(),
            parameters: spec.parameters,
            functions: spec
                .functions
                .into_iter()
                .map(FunctionMetadata::from)
                .collect(),
        }
    }
}

impl From<&ModuleSpec> for ModuleMetadata {
    fn from(spec: &ModuleSpec) -> Self {
        Self {
            name: spec.name.clone(),
            doc: spec.doc.clone(),
            capabilities: spec.capabilities.iter().map(|c| c.label.clone()).collect(),
            parameters: spec.parameters.clone(),
            functions: spec
                .functions
                .iter()
                .map(|f| FunctionMetadata::from(f.clone()))
                .collect(),
        }
    }
}

pub trait Module: Send + Sync {
    fn spec(&self) -> ModuleSpec;

    fn metadata(&self) -> ModuleMetadata {
        ModuleMetadata::from(self.spec())
    }

    fn register(
        &self,
        runtime: &mut Runtime<roto::Ctx<ExecutionContext>>,
        params: &std::collections::HashMap<String, String>,
    ) -> Result<()>;

    fn close(&self) -> Result<()> {
        Ok(())
    }
}

pub(crate) type ModuleRef = Arc<dyn Module>;
type RegisterFn = dyn Fn(
        &mut Runtime<roto::Ctx<ExecutionContext>>,
        &std::collections::HashMap<String, String>,
    ) -> Result<()>
    + Send
    + Sync;

pub struct FunctionModule {
    spec: ModuleSpec,
    register_fn: Arc<RegisterFn>,
    close_fn: Arc<dyn Fn() -> Result<()> + Send + Sync>,
}

impl FunctionModule {
    pub fn new(
        spec: ModuleSpec,
        register_fn: impl Fn(
            &mut Runtime<roto::Ctx<ExecutionContext>>,
            &std::collections::HashMap<String, String>,
        ) -> Result<()>
        + Send
        + Sync
        + 'static,
        close_fn: Arc<dyn Fn() -> Result<()> + Send + Sync>,
    ) -> Self {
        Self {
            spec,
            register_fn: Arc::new(register_fn),
            close_fn,
        }
    }

    pub fn builder(name: impl Into<String>) -> FunctionModuleBuilder {
        FunctionModuleBuilder::new(name)
    }
}

impl Module for FunctionModule {
    fn spec(&self) -> ModuleSpec {
        self.spec.clone()
    }

    fn register(
        &self,
        runtime: &mut Runtime<roto::Ctx<ExecutionContext>>,
        params: &std::collections::HashMap<String, String>,
    ) -> Result<()> {
        (self.register_fn)(runtime, params)
    }

    fn close(&self) -> Result<()> {
        (self.close_fn)()
    }
}

pub struct FunctionModuleBuilder {
    name: String,
    doc: String,
    capabilities: Vec<CapabilityMetadata>,
    parameters: Vec<ModuleParameter>,
    functions: Vec<FunctionSpec>,
    close_fn: Option<Arc<dyn Fn() -> Result<()> + Send + Sync>>,
}

impl FunctionModuleBuilder {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            doc: String::new(),
            capabilities: Vec::new(),
            parameters: Vec::new(),
            functions: Vec::new(),
            close_fn: None,
        }
    }

    pub fn doc(mut self, doc: impl Into<String>) -> Self {
        self.doc = doc.into();
        self
    }

    pub fn capability(mut self, cap: CapabilityMetadata) -> Self {
        self.capabilities.push(cap);
        self
    }

    pub fn parameter(mut self, param: ModuleParameter) -> Self {
        self.parameters.push(param);
        self
    }

    pub fn function(mut self, spec: FunctionSpec) -> Self {
        self.functions.push(spec);
        self
    }

    pub fn on_close(mut self, f: impl Fn() -> Result<()> + Send + Sync + 'static) -> Self {
        self.close_fn = Some(Arc::new(f));
        self
    }

    pub fn function_spec(
        mut self,
        function_name: &str,
        doc: impl Into<String>,
        args: Vec<String>,
        return_type: impl Into<String>,
    ) -> Self {
        self.functions.push(FunctionSpec::new(
            &self.name,
            function_name,
            doc,
            args,
            return_type,
        ));
        self
    }

    pub fn build<F>(self, register_fn: F) -> FunctionModule
    where
        F: Fn(
                &mut Runtime<roto::Ctx<ExecutionContext>>,
                &std::collections::HashMap<String, String>,
            ) -> Result<()>
            + Send
            + Sync
            + 'static,
    {
        FunctionModule::new(
            ModuleSpec {
                name: self.name,
                doc: self.doc,
                capabilities: self.capabilities,
                parameters: self.parameters,
                functions: self.functions,
            },
            register_fn,
            self.close_fn.unwrap_or_else(|| Arc::new(|| Ok(()))),
        )
    }
}

pub fn qualified_name(module_name: &str, function_name: &str) -> String {
    format!("{module_name}_{function_name}")
}

pub fn register_fn<F, A, R, O>(
    runtime: &mut Runtime<roto::Ctx<ExecutionContext>>,
    name: &str,
    doc: &str,
    args: &[&str],
    func: F,
) -> Result<()>
where
    F: roto::RegisterableFn<A, R, O>,
{
    let function =
        roto::Function::new(name, doc, args.to_vec(), func, roto::location!()).map_err(|err| {
            Error::runtime_contract(err.to_string())
                .with_primary_diagnostic("function-definition-failed")
        })?;
    runtime.add(function).map_err(|err| {
        Error::runtime_contract(err.to_string())
            .with_primary_diagnostic("function-registration-failed")
    })
}
