use std::collections::HashMap;
use std::default::Default;
use std::fmt::Debug;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::authorization::permissions::{PermRule, PermissionBuf, PrivilegesBuf};
use crate::authorization::roles::Role;
use crate::capnp::{Listen, TlsListen};
use crate::logging::LogConfig;

use std::path::Path;

#[derive(Debug)]
struct DhallConfig<'a> {
    path: &'a Path,
}

pub fn read_config_file(path: impl AsRef<Path>) -> Result<Config, serde_dhall::Error> {
    serde_dhall::from_file(path).parse().map_err(Into::into)
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
/// A description of a machine
///
/// This is the struct that a machine is serialized to/from.
/// Combining this with the actual state of the system will return a machine
pub struct MachineDescription {
    /// The name of the machine. Doesn't need to be unique but is what humans will be presented.
    pub name: String,

    /// An optional description of the Machine.
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deser_option"
    )]
    pub description: Option<String>,

    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deser_option"
    )]
    pub wiki: Option<String>,

    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        deserialize_with = "deser_option"
    )]
    pub category: Option<String>,

    /// The permission required
    #[serde(flatten)]
    pub privs: PrivilegesBuf,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    pub spacename: String,

    pub instanceurl: String,

    /// A list of address/port pairs to listen on.
    pub listens: Vec<Listen>,

    #[serde(flatten)]
    pub tlsconfig: TlsListen,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tlskeylog: Option<PathBuf>,

    #[serde(default, skip)]
    pub verbosity: isize,

    #[serde(default, skip)]
    pub logging: LogConfig,

    pub mqtt_url: String,
    pub db_path: PathBuf,
    pub auditlog_path: PathBuf,

    pub roles: HashMap<String, Role>,

    /// Machine descriptions to load
    pub machines: HashMap<String, MachineDescription>,

    /// Actors to load and their configuration options
    pub actors: HashMap<String, ModuleConfig>,
    pub actor_connections: Vec<ActorConnectionConfig>,

    /// Initiators to load and their configuration options
    pub initiators: HashMap<String, ModuleConfig>,
    pub init_connections: Vec<InitiatorConnectionConfig>,
}

impl Config {
    pub fn is_quiet(&self) -> bool {
        self.verbosity < 0
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleConfig {
    pub module: String,
    pub params: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParamsConfig {
    pub module: String,
    pub params: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActorConnectionConfig {
    pub machine: String,
    pub actor: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitiatorConnectionConfig {
    pub machine: String,
    pub initiator: String,
}

pub(crate) fn deser_option<'de, D, T>(d: D) -> std::result::Result<Option<T>, D::Error>
where
    D: serde::Deserializer<'de>,
    T: serde::Deserialize<'de>,
{
    Ok(T::deserialize(d).ok())
}

impl Default for Config {
    fn default() -> Self {
        let mut actors: HashMap<String, ModuleConfig> = HashMap::new();
        let mut initiators: HashMap<String, ModuleConfig> = HashMap::new();
        let mut roles: HashMap<String, Role> = HashMap::new();
        let mut machines: HashMap<String, MachineDescription> = HashMap::new();

        let mut initiator_123_params: HashMap<String, String> = HashMap::new();
        initiator_123_params.insert("args".to_string(), "".to_string());
        initiator_123_params.insert("cmd".to_string(), "echo".to_string());

        let mut actor_connections_vec: Vec<ActorConnectionConfig> = vec![ActorConnectionConfig {
            machine: "resource_a".to_string(),
            actor: "actor_123".to_string(),
        }];

        let mut initiator_connections_vec: Vec<InitiatorConnectionConfig> =
            vec![InitiatorConnectionConfig {
                machine: "resource_a".to_string(),
                initiator: "initiator_123".to_string(),
            }];

        roles.insert(
            "admin".to_string(),
            Role {
                parents: Vec::new(),
                permissions: vec![
                    PermRule::Base(PermissionBuf {
                        inner: "bffh.users.info".to_string(),
                    }),
                    PermRule::Base(PermissionBuf {
                        inner: "bffh.users.manage".to_string(),
                    }),
                    PermRule::Base(PermissionBuf {
                        inner: "bffh.users.admin".to_string(),
                    }),
                ],
            },
        );

        roles.insert(
            "member".to_string(),
            Role {
                parents: Vec::new(),
                permissions: vec![
                    PermRule::Base(PermissionBuf {
                        inner: "lab.some.disclose".to_string(),
                    }),
                    PermRule::Base(PermissionBuf {
                        inner: "lab.some.read".to_string(),
                    }),
                    PermRule::Base(PermissionBuf {
                        inner: "lab.some.write".to_string(),
                    }),
                    PermRule::Base(PermissionBuf {
                        inner: "lab.some.manage".to_string(),
                    }),
                ],
            },
        );

        machines.insert(
            "resource_a".to_string(),
            MachineDescription {
                name: "Resource A".to_string(),
                description: Option::from("A description".to_string()),
                wiki: Option::from("https://some.wiki.url".to_string()),
                category: Option::from("A category".to_string()),
                privs: PrivilegesBuf {
                    disclose: PermissionBuf {
                        inner: "lab.some.disclose".to_string(),
                    },
                    read: PermissionBuf {
                        inner: "lab.some.read".to_string(),
                    },
                    write: PermissionBuf {
                        inner: "lab.some.write".to_string(),
                    },
                    manage: PermissionBuf {
                        inner: "lab.some.manage".to_string(),
                    },
                },
            },
        );

        machines.insert(
            "resource_b".to_string(),
            MachineDescription {
                name: "Resource B".to_string(),
                description: Option::from("A description".to_string()),
                wiki: Option::from("https://some.wiki.url".to_string()),
                category: Option::from("A category".to_string()),
                privs: PrivilegesBuf {
                    disclose: PermissionBuf {
                        inner: "lab.some.disclose".to_string(),
                    },
                    read: PermissionBuf {
                        inner: "lab.some.read".to_string(),
                    },
                    write: PermissionBuf {
                        inner: "lab.some.write".to_string(),
                    },
                    manage: PermissionBuf {
                        inner: "lab.some.manage".to_string(),
                    },
                },
            },
        );

        actors.insert(
            "actor_123".to_string(),
            ModuleConfig {
                module: "Shelly".to_string(),
                params: HashMap::new(),
            },
        );

        initiators.insert(
            "initiator_123".to_string(),
            ModuleConfig {
                module: "Process".to_string(),
                params: initiator_123_params,
            },
        );

        Config {
            spacename: "fabaccess.sample.space".into(),
            instanceurl: "https://fabaccess.sample.space".into(),
            listens: vec![Listen {
                address: "127.0.0.1".to_string(),
                port: None,
            }],
            tlsconfig: TlsListen {
                certfile: PathBuf::from("/etc/bffh/certs/bffh.crt"),
                keyfile: PathBuf::from("/etc/bffh/certs/bffh.key"),
                ..Default::default()
            },
            tlskeylog: None,
            verbosity: 0,
            logging: LogConfig::default(),
            mqtt_url: "mqtt://127.0.0.1:1883".to_string(),
            db_path: PathBuf::from("/var/lib/bffh/bffh.db"),
            auditlog_path: PathBuf::from("/var/log/bffh/audit.json"),
            roles,
            machines,
            actors,
            actor_connections: actor_connections_vec,
            initiators,
            init_connections: initiator_connections_vec,
        }
    }
}