use async_trait::async_trait;
use std::collections::HashMap;

use time::OffsetDateTime;
use uuid::Uuid;

pub use crate::APIInterface;
use crate::{
    api::event_socket::{EventSocket, SinkMessage, SocketEvent, SocketUpdate},
    api::http_client::Credentials,
    model::{
        Conversation, ConversationID, Event, JwtToken, Message, MessageID, OutgoingMessage,
        UpdateItem,
    },
};

use bytes::Bytes;
use futures_util::stream::BoxStream;
use futures_util::Sink;
use futures_util::StreamExt;

pub struct TestClient {
    pub version: &'static str,
    pub conversations: Vec<Conversation>,
    pub messages: HashMap<ConversationID, Vec<Message>>,
}

#[derive(Debug)]
pub enum TestError {
    ConversationNotFound,
}

impl TestClient {
    pub fn new() -> TestClient {
        TestClient {
            version: "KordophoneTest-1.0",
            conversations: vec![],
            messages: HashMap::<ConversationID, Vec<Message>>::new(),
        }
    }
}

pub struct TestEventSocket {
    pub events: Vec<Event>,
}

impl TestEventSocket {
    pub fn new() -> Self {
        Self { events: vec![] }
    }
}

#[async_trait]
impl EventSocket for TestEventSocket {
    type Error = TestError;
    type EventStream = BoxStream<'static, Result<SocketEvent, TestError>>;
    type UpdateStream = BoxStream<'static, Result<SocketUpdate, TestError>>;

    async fn events(
        self,
    ) -> (
        Self::EventStream,
        impl Sink<SinkMessage, Error = Self::Error>,
    ) {
        (
            futures_util::stream::iter(self.events.into_iter().map(Ok)).boxed(),
            futures_util::sink::sink(),
        )
    }

    async fn raw_updates(self) -> Self::UpdateStream {
        let results: Vec<Result<Vec<UpdateItem>, TestError>> = vec![];
        futures_util::stream::iter(results.into_iter()).boxed()
    }
}

#[async_trait]
impl APIInterface for TestClient {
    type Error = TestError;
    type ResponseStream = BoxStream<'static, Result<Bytes, TestError>>;

    async fn authenticate(&mut self, _credentials: Credentials) -> Result<JwtToken, Self::Error> {
        Ok(JwtToken::dummy())
    }

    async fn get_version(&mut self) -> Result<String, Self::Error> {
        Ok(self.version.to_string())
    }

    async fn get_conversations(&mut self) -> Result<Vec<Conversation>, Self::Error> {
        Ok(self.conversations.clone())
    }

    async fn get_messages(
        &mut self,
        conversation_id: &ConversationID,
        limit: Option<u32>,
        before: Option<MessageID>,
        after: Option<MessageID>,
    ) -> Result<Vec<Message>, Self::Error> {
        if let Some(messages) = self.messages.get(conversation_id) {
            return Ok(messages.clone());
        }

        Err(TestError::ConversationNotFound)
    }

    async fn send_message(
        &mut self,
        outgoing_message: &OutgoingMessage,
    ) -> Result<Message, Self::Error> {
        let message = Message::builder()
            .guid(Uuid::new_v4().to_string())
            .text(outgoing_message.text.clone())
            .date(OffsetDateTime::now_utc())
            .build();

        self.messages
            .entry(outgoing_message.conversation_id.clone())
            .or_insert(vec![])
            .push(message.clone());
        Ok(message)
    }

    async fn open_event_socket(
        &mut self,
        _update_seq: Option<u64>,
    ) -> Result<impl EventSocket, Self::Error> {
        Ok(TestEventSocket::new())
    }

    async fn fetch_attachment_data(
        &mut self,
        guid: &str,
        preview: bool,
    ) -> Result<Self::ResponseStream, Self::Error> {
        Ok(futures_util::stream::iter(vec![Ok(Bytes::from_static(b"test"))]).boxed())
    }

    async fn upload_attachment<R>(
        &mut self,
        data: tokio::io::BufReader<R>,
        filename: &str,
        size: u64,
    ) -> Result<String, Self::Error>
    where
        R: tokio::io::AsyncRead + Unpin + Send + Sync + 'static,
    {
        Ok(String::from("test"))
    }

    async fn mark_conversation_as_read(
        &mut self,
        conversation_id: &ConversationID,
    ) -> Result<(), Self::Error> {
        Ok(())
    }
}
