use crate::http_client::HttpClient;
use crate::proto::rice_db_client::RiceDbClient;
use crate::proto::*;
use rice_core::error::RiceError;
use std::str::FromStr;
use tonic::codegen::InterceptedService;
use tonic::metadata::MetadataValue;
use tonic::service::Interceptor;
use tonic::transport::Channel;
use tonic::Status;

#[derive(Clone)]
struct AuthInterceptor {
    token: MetadataValue<tonic::metadata::Ascii>,
}

impl Interceptor for AuthInterceptor {
    fn call(&mut self, mut req: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
        req.metadata_mut()
            .insert("authorization", self.token.clone());
        Ok(req)
    }
}

#[derive(Clone)]
enum ClientImpl {
    Grpc(Channel),
    GrpcAuth {
        service: InterceptedService<Channel, AuthInterceptor>,
        channel: Channel,
    },
    Http(HttpClient),
}

#[derive(Clone)]
pub struct StorageClient {
    inner: ClientImpl,
}

impl StorageClient {
    pub async fn connect(dst: String) -> Result<Self, RiceError> {
        let channel = Channel::from_shared(dst)
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?
            .connect()
            .await?;
        Ok(Self {
            inner: ClientImpl::Grpc(channel),
        })
    }

    pub async fn connect_with_token(dst: String, token: String) -> Result<Self, RiceError> {
        let channel = Channel::from_shared(dst)
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?
            .connect()
            .await?;

        let token_meta = MetadataValue::from_str(&format!("Bearer {}", token))
            .map_err(|e| RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)))?;

        let interceptor = AuthInterceptor { token: token_meta };
        let service = InterceptedService::new(channel.clone(), interceptor);

        Ok(Self {
            inner: ClientImpl::GrpcAuth { service, channel },
        })
    }

    pub fn connect_http(host: String, port: u16, token: Option<String>) -> Result<Self, RiceError> {
        let client = HttpClient::new(&host, port, token)?;
        Ok(Self {
            inner: ClientImpl::Http(client),
        })
    }

    pub fn connect_http_url(url: String, token: Option<String>) -> Result<Self, RiceError> {
        let client = HttpClient::new_with_url(&url, token)?;
        Ok(Self {
            inner: ClientImpl::Http(client),
        })
    }

    pub async fn insert(
        &mut self,
        id: u64,
        text: String,
        metadata: Vec<u8>,
        user_id: u64,
        session_id: Option<String>,
    ) -> Result<InsertResponse, RiceError> {
        match &mut self.inner {
            ClientImpl::Grpc(channel) => {
                let mut client = RiceDbClient::new(channel.clone());
                let request = InsertRequest {
                    id,
                    text,
                    metadata,
                    user_id,
                    session_id,
                };
                let response = client.insert(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::GrpcAuth { service, .. } => {
                let mut client = RiceDbClient::new(service.clone());
                let request = InsertRequest {
                    id,
                    text,
                    metadata,
                    user_id,
                    session_id,
                };
                let response = client.insert(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::Http(client) => {
                client.insert(id, text, metadata, user_id, session_id).await
            }
        }
    }

    pub async fn search(
        &mut self,
        query_text: String,
        user_id: u64,
        k: u32,
        session_id: Option<String>,
        filter: Option<String>,
    ) -> Result<SearchResponse, RiceError> {
        match &mut self.inner {
            ClientImpl::Grpc(channel) => {
                let mut client = RiceDbClient::new(channel.clone());
                let request = SearchRequest {
                    query_text,
                    user_id,
                    k,
                    session_id,
                    filter: filter.unwrap_or_default(),
                };
                let response = client.search(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::GrpcAuth { service, .. } => {
                let mut client = RiceDbClient::new(service.clone());
                let request = SearchRequest {
                    query_text,
                    user_id,
                    k,
                    session_id,
                    filter: filter.unwrap_or_default(),
                };
                let response = client.search(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::Http(client) => {
                client
                    .search(query_text, user_id, k, session_id, filter)
                    .await
            }
        }
    }

    pub async fn login(
        &mut self,
        username: String,
        password: String,
    ) -> Result<LoginResponse, RiceError> {
        let inner_clone = self.inner.clone();
        match inner_clone {
            ClientImpl::Grpc(channel) => {
                let mut client = RiceDbClient::new(channel.clone());
                let request = LoginRequest { username, password };
                let response = client.login(request).await?;
                let login_response = response.into_inner();

                let token = login_response.token.clone();

                let token_meta =
                    MetadataValue::from_str(&format!("Bearer {}", token)).map_err(|e| {
                        RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
                    })?;
                let interceptor = AuthInterceptor { token: token_meta };
                let service = InterceptedService::new(channel.clone(), interceptor);

                self.inner = ClientImpl::GrpcAuth {
                    service,
                    channel: channel.clone(),
                };
                Ok(login_response)
            }
            ClientImpl::GrpcAuth { channel, .. } => {
                let mut client = RiceDbClient::new(channel.clone());
                let request = LoginRequest { username, password };
                let response = client.login(request).await?;
                let login_response = response.into_inner();

                let token = login_response.token.clone();

                let token_meta =
                    MetadataValue::from_str(&format!("Bearer {}", token)).map_err(|e| {
                        RiceError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
                    })?;
                let interceptor = AuthInterceptor { token: token_meta };
                let service = InterceptedService::new(channel.clone(), interceptor);

                self.inner = ClientImpl::GrpcAuth {
                    service,
                    channel: channel.clone(),
                };
                Ok(login_response)
            }
            ClientImpl::Http(mut client) => {
                let res = client.login(username, password).await;
                if res.is_ok() {
                    self.inner = ClientImpl::Http(client);
                }
                res
            }
        }
    }

    pub async fn health(&mut self) -> Result<HealthResponse, RiceError> {
        match &mut self.inner {
            ClientImpl::Grpc(channel) => {
                let mut client = RiceDbClient::new(channel.clone());
                let request = HealthRequest {};
                let response = client.health(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::GrpcAuth { service, .. } => {
                let mut client = RiceDbClient::new(service.clone());
                let request = HealthRequest {};
                let response = client.health(request).await?;
                Ok(response.into_inner())
            }
            ClientImpl::Http(client) => client.health().await,
        }
    }
}
