// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{
    path::PathBuf,
    sync::{atomic::AtomicU64, Arc},
};

use tokio::sync::RwLock;
use zbus::{
    interface,
    zvariant::{Fd, ObjectPath, OwnedObjectPath},
    Connection, Result,
};

use crate::{
    dbus::{
        consts,
        manager::Manager,
        manager::{
            manager_3_0::{
                create_pool_method, destroy_pool_method, engine_state_report_method,
                list_keys_method, set_key_method, unset_key_method, version_prop,
            },
            manager_3_2::{refresh_state_method, stopped_pools_prop},
            manager_3_4::start_pool_method,
            manager_3_6::stop_pool_method,
        },
        types,
    },
    engine::{Engine, KeyDescription, Lockable, StoppedPoolsInfo, UnlockMethod},
};

pub struct ManagerR7 {
    connection: Arc<Connection>,
    engine: Arc<dyn Engine>,
    manager: Lockable<Arc<RwLock<Manager>>>,
    counter: Arc<AtomicU64>,
}

impl ManagerR7 {
    pub fn new(
        engine: Arc<dyn Engine>,
        connection: Arc<Connection>,
        manager: Lockable<Arc<RwLock<Manager>>>,
        counter: Arc<AtomicU64>,
    ) -> Self {
        ManagerR7 {
            connection,
            engine,
            manager,
            counter,
        }
    }

    pub async fn register(
        engine: &Arc<dyn Engine>,
        connection: &Arc<Connection>,
        manager: &Lockable<Arc<RwLock<Manager>>>,
        counter: &Arc<AtomicU64>,
    ) -> Result<()> {
        let manager = Self::new(
            Arc::clone(engine),
            Arc::clone(connection),
            manager.clone(),
            Arc::clone(counter),
        );
        connection
            .object_server()
            .at(consts::STRATIS_BASE_PATH, manager)
            .await?;
        Ok(())
    }
}

#[interface(name = "org.storage.stratis3.Manager.r7")]
impl ManagerR7 {
    #[zbus(property(emits_changed_signal = "const"))]
    #[allow(clippy::unused_self)]
    fn version(&self) -> &str {
        version_prop()
    }

    #[zbus(property(emits_changed_signal = "true"))]
    async fn stopped_pools(&self) -> types::ManagerR2<StoppedPoolsInfo> {
        stopped_pools_prop(&self.engine).await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn list_keys(&self) -> (Vec<KeyDescription>, u16, String) {
        list_keys_method(&self.engine).await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn set_key(
        &self,
        key_desc: KeyDescription,
        key_fd: Fd<'_>,
    ) -> ((bool, bool), u16, String) {
        set_key_method(&self.engine, &key_desc, key_fd).await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn unset_key(&self, key_desc: KeyDescription) -> (bool, u16, String) {
        unset_key_method(&self.engine, &key_desc).await
    }

    #[allow(clippy::too_many_arguments)]
    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn create_pool(
        &self,
        name: &str,
        devices: Vec<PathBuf>,
        key_desc: (bool, KeyDescription),
        clevis_info: (bool, (&str, &str)),
    ) -> ((bool, (OwnedObjectPath, Vec<OwnedObjectPath>)), u16, String) {
        create_pool_method(
            &self.engine,
            &self.connection,
            &self.manager,
            &self.counter,
            name,
            devices,
            key_desc,
            clevis_info,
        )
        .await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn destroy_pool(&self, pool: ObjectPath<'_>) -> ((bool, String), u16, String) {
        destroy_pool_method(&self.engine, &self.connection, &self.manager, pool).await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn start_pool(
        &self,
        id: &str,
        id_type: &str,
        unlock_method: (bool, UnlockMethod),
    ) -> (
        (
            bool,
            (OwnedObjectPath, Vec<OwnedObjectPath>, Vec<OwnedObjectPath>),
        ),
        u16,
        String,
    ) {
        start_pool_method(
            &self.engine,
            &self.connection,
            &self.manager,
            &self.counter,
            id,
            id_type,
            unlock_method,
        )
        .await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    async fn stop_pool(&self, id: &str, id_type: &str) -> ((bool, String), u16, String) {
        stop_pool_method(&self.engine, &self.connection, &self.manager, id, id_type).await
    }

    #[zbus(out_args("return_code", "return_string"))]
    async fn refresh_state(&self) -> (u16, String) {
        refresh_state_method(&self.engine).await
    }

    #[zbus(out_args("result", "return_code", "return_string"))]
    fn engine_state_report(&self) -> (String, u16, String) {
        engine_state_report_method(&self.engine)
    }
}
