use super::LightString;
use crate::Bytes;
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::convert::AsRef;

pub type ApiResult<Output, CallError> = Result<Response<Output>, ApiErr<CallError>>;

#[derive(thiserror::Error, Debug)]
pub enum ApiErr<E> {
    #[error("序列化数据失败,{0}")]
    SerializeError(serde_json::Error),
    #[error("反序列化数据失败,{0}")]
    DeserializeError(serde_json::Error),
    #[error("调用远程接口失败,{0}")]
    CallError(E),
}

#[async_trait]
pub trait ApiClient {
    type Output: AsRef<[u8]>;
    type Error;
    async fn request(&self, namespace: &str, input: Bytes) -> Result<Self::Output, Self::Error>;
}

#[async_trait]
pub trait Api {
    type Input;
    type Output;
    fn namespace() -> LightString;
    fn require_res_key() -> Option<LightString> {
        return None;
    }
    fn validate_input(_: &Self::Input) -> Result<(), LightString> {
        return Ok(());
    }
    async fn call<Client, ClientOutput, E>(
        &self,
        client: &Client,
        input: &Self::Input,
    ) -> Result<Response<Self::Output>, ApiErr<E>>
    where
        Client: ApiClient<Error = E, Output = ClientOutput> + Sync,
        ClientOutput: AsRef<[u8]>,
        Self::Input: Serialize + Sync,
        Self::Output: DeserializeOwned + 'static,
    {
        let input = serde_json::to_vec(input).map_err(ApiErr::SerializeError)?;
        let output = client
            .request(&Self::namespace(), input.into())
            .await
            .map_err(ApiErr::CallError)?;
        let response = serde_json::from_slice::<Response<Self::Output>>(output.as_ref())
            .map_err(ApiErr::DeserializeError)?;
        return Ok(response);
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Response<T> {
    pub code: i32,
    pub data: Option<T>,
    pub message: LightString,
}

impl<T> Response<T> {
    pub fn success(data: Option<T>) -> Response<T> {
        return Response {
            code: 0,
            data: data,
            message: LightString::from_static("success"),
        };
    }
    pub fn failure(mut code: i32, msg: LightString, data: Option<T>) -> Response<T> {
        if 0 == code {
            code = -1;
        }
        return Response {
            code: code,
            data: data,
            message: msg,
        };
    }
}

pub fn success() -> &'static [u8] {
    return b"{\"code\":0,\"message\":\"success\",\"data\":null}";
}
