use crate::config::config_type::ConfigType;
use crate::config::core::{ConfigHistoryInfoDto, ConfigKey, ConfigValue};
use crate::utils::get_md5;
use actix::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Message)]
#[rtype(result = "anyhow::Result<ConfigRaftResult>")]
pub enum ConfigRaftCmd {
    ConfigAdd {
        key: String,
        value: Arc<String>,
        config_type: Option<Arc<String>>,
        desc: Option<Arc<String>>,
        history_id: u64,
        history_table_id: Option<u64>,
        op_time: i64,
        op_user: Option<Arc<String>>,
    },
    ConfigRemove {
        key: String,
    },
    SetFullValue {
        key: ConfigKey,
        value: ConfigValue,
        last_id: Option<u64>,
    },
}

#[derive(Debug)]
pub struct SetConfigParam {
    pub key: ConfigKey,
    pub value: Arc<String>,
    pub config_type: Option<Arc<String>>,
    pub desc: Option<Arc<String>>,
    pub history_id: u64,
    pub history_table_id: Option<u64>,
    pub op_time: i64,
    pub op_user: Option<Arc<String>>,
}

pub enum ConfigRaftResult {
    Snapshot {
        data: Vec<(ConfigKey, Arc<String>)>,
        history_table_id: u64,
    },
    None,
}

#[derive(Clone)]
pub struct HistoryItem {
    pub id: u64,
    pub content: Arc<String>,
    pub modified_time: i64,
    pub op_user: Option<Arc<String>>,
}

impl HistoryItem {
    pub(crate) fn to_dto(&self, key: &ConfigKey) -> ConfigHistoryInfoDto {
        ConfigHistoryInfoDto {
            id: Some(self.id as i64),
            tenant: Some(key.tenant.to_string()),
            group: Some(key.group.to_string()),
            data_id: Some(key.data_id.to_string()),
            content: Some(self.content.to_string()),
            modified_time: Some(self.modified_time),
        }
    }
}

#[derive(Clone, PartialEq, prost_derive::Message, Deserialize, Serialize)]
pub struct ConfigHistoryItemDO {
    #[prost(uint64, optional, tag = "1")]
    pub id: Option<u64>,
    #[prost(string, optional, tag = "2")]
    pub content: Option<String>,
    #[prost(int64, optional, tag = "3")]
    pub last_time: Option<i64>,
    #[prost(string, optional, tag = "4")]
    pub op_user: Option<String>,
}

impl From<HistoryItem> for ConfigHistoryItemDO {
    fn from(value: HistoryItem) -> Self {
        Self {
            id: Some(value.id),
            content: Some(value.content.as_ref().to_string()),
            last_time: Some(value.modified_time),
            op_user: value.op_user.map(|e| e.as_ref().to_string()),
        }
    }
}

impl From<ConfigHistoryItemDO> for HistoryItem {
    fn from(value: ConfigHistoryItemDO) -> Self {
        Self {
            id: value.id.unwrap_or_default(),
            content: Arc::new(value.content.unwrap_or_default()),
            modified_time: value.last_time.unwrap_or_default(),
            op_user: value.op_user.map(Arc::new),
        }
    }
}
#[derive(Clone, PartialEq, prost_derive::Message, Deserialize, Serialize)]
pub struct ConfigValueDO {
    #[prost(string, optional, tag = "1")]
    pub content: Option<String>,
    #[prost(repeated, message, tag = "2")]
    pub histories: Vec<ConfigHistoryItemDO>,
    #[prost(string, optional, tag = "3")]
    pub config_type: Option<String>,
    #[prost(string, optional, tag = "4")]
    pub desc: Option<String>,
}

impl ConfigValueDO {
    pub fn to_bytes(&self) -> anyhow::Result<Vec<u8>> {
        use prost::Message;
        let mut v = Vec::new();
        self.encode(&mut v)?;
        Ok(v)
    }

    pub fn from_bytes(data: &[u8]) -> anyhow::Result<Self> {
        use prost::Message;
        let s = Self::decode(data)?;
        Ok(s)
    }
}

impl From<ConfigValue> for ConfigValueDO {
    fn from(value: ConfigValue) -> Self {
        Self {
            content: Some(value.content.as_ref().to_owned()),
            histories: value.histories.into_iter().map(|e| e.into()).collect(),
            config_type: value.config_type.map(|e| e.as_ref().to_owned()),
            desc: value.desc.map(|e| e.as_ref().to_owned()),
        }
    }
}

impl From<ConfigValueDO> for ConfigValue {
    fn from(value: ConfigValueDO) -> Self {
        let content = value.content.unwrap_or_default();
        let md5 = Arc::new(get_md5(&content));
        let last_modified =
            if let Some(Some(v)) = value.histories.iter().last().map(|e| e.last_time) {
                v
            } else {
                0
            };
        Self {
            content: Arc::new(content),
            md5,
            tmp: false,
            histories: value.histories.into_iter().map(|e| e.into()).collect(),
            config_type: value
                .config_type
                .map(|v| ConfigType::new_by_value(&v).get_value()),
            desc: value.desc.map(Arc::new),
            last_modified,
        }
    }
}
