//! Device registry RPC handlers: registerDevice, listDevices, revokeDevice.

use capnp::capability::Promise;
use quicproquo_proto::node_capnp::node_service;

use crate::auth::{coded_error, require_identity, validate_auth_context};
use crate::error_codes::*;
use crate::storage::StorageError;

use super::NodeServiceImpl;

const MAX_DEVICES_PER_IDENTITY: usize = 5;

fn storage_err(err: StorageError) -> capnp::Error {
    coded_error(E009_STORAGE_ERROR, err)
}

impl NodeServiceImpl {
    pub fn handle_register_device(
        &mut self,
        params: node_service::RegisterDeviceParams,
        mut results: node_service::RegisterDeviceResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        let auth_ctx = match validate_auth_context(&self.auth_cfg, &self.sessions, p.get_auth()) {
            Ok(ctx) => ctx,
            Err(e) => return Promise::err(e),
        };

        let identity_key = match require_identity(&auth_ctx) {
            Ok(ik) => ik.to_vec(),
            Err(e) => return Promise::err(e),
        };

        let device_id = match p.get_device_id() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        if device_id.is_empty() {
            return Promise::err(coded_error(E020_BAD_PARAMS, "deviceId must not be empty"));
        }

        let device_name = match p.get_device_name() {
            Ok(n) => match n.to_str() {
                Ok(s) => s.to_string(),
                Err(_) => return Promise::err(coded_error(E020_BAD_PARAMS, "deviceName must be valid UTF-8")),
            },
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        // Check device limit.
        match self.store.device_count(&identity_key) {
            Ok(count) if count >= MAX_DEVICES_PER_IDENTITY => {
                return Promise::err(coded_error(
                    E029_DEVICE_LIMIT,
                    format!("maximum {MAX_DEVICES_PER_IDENTITY} devices per identity"),
                ));
            }
            Err(e) => return Promise::err(storage_err(e)),
            _ => {}
        }

        match self.store.register_device(&identity_key, &device_id, &device_name) {
            Ok(success) => {
                results.get().set_success(success);
                Promise::ok(())
            }
            Err(e) => Promise::err(storage_err(e)),
        }
    }

    pub fn handle_list_devices(
        &mut self,
        params: node_service::ListDevicesParams,
        mut results: node_service::ListDevicesResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        let auth_ctx = match validate_auth_context(&self.auth_cfg, &self.sessions, p.get_auth()) {
            Ok(ctx) => ctx,
            Err(e) => return Promise::err(e),
        };

        let identity_key = match require_identity(&auth_ctx) {
            Ok(ik) => ik.to_vec(),
            Err(e) => return Promise::err(e),
        };

        let devices = match self.store.list_devices(&identity_key) {
            Ok(d) => d,
            Err(e) => return Promise::err(storage_err(e)),
        };

        let r = results.get();
        let mut list = r.init_devices(devices.len() as u32);
        for (i, (device_id, name, registered_at)) in devices.iter().enumerate() {
            let mut entry = list.reborrow().get(i as u32);
            entry.set_device_id(device_id);
            entry.set_device_name(name);
            entry.set_registered_at(*registered_at);
        }

        Promise::ok(())
    }

    pub fn handle_revoke_device(
        &mut self,
        params: node_service::RevokeDeviceParams,
        mut results: node_service::RevokeDeviceResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        let auth_ctx = match validate_auth_context(&self.auth_cfg, &self.sessions, p.get_auth()) {
            Ok(ctx) => ctx,
            Err(e) => return Promise::err(e),
        };

        let identity_key = match require_identity(&auth_ctx) {
            Ok(ik) => ik.to_vec(),
            Err(e) => return Promise::err(e),
        };

        let device_id = match p.get_device_id() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        if device_id.is_empty() {
            return Promise::err(coded_error(E020_BAD_PARAMS, "deviceId must not be empty"));
        }

        match self.store.revoke_device(&identity_key, &device_id) {
            Ok(true) => {
                results.get().set_success(true);
                Promise::ok(())
            }
            Ok(false) => {
                Promise::err(coded_error(E030_DEVICE_NOT_FOUND, "device not found"))
            }
            Err(e) => Promise::err(storage_err(e)),
        }
    }
}
