use crate::model::event::{ChatEvent, ChatMsg};
use crate::error::{ApiResult, AppError};
use crate::repo::message_repo::MessageRepository;
use crate::repo::{ChannelRepo, Repo, SpaceRepo, UserRepo};
use chrono::{DateTime, Utc};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use std::sync::mpsc::channel;
use std::time::Instant;
use tokio::sync::broadcast::Sender;
use tokio::sync::{broadcast, Mutex};
use uuid::Uuid;
use crate::model::space::SpaceDto;
use crate::svc::llm_service::LlmService;

/// ---------- shared broadcaster ----------

#[derive(Clone)]
pub struct ChatService {
    users: Arc<dyn UserRepo>,
    channel_repo: Arc<dyn ChannelRepo>,
    spaces: Arc<dyn SpaceRepo>,
    messages: MessageRepository,

    llm: LlmService,
    buffer_size: usize,
    channels: Arc<Mutex<HashMap<i64, ChannelState>>>,
}

impl ChatService {
    pub fn new(
        buffer_size: usize, llm: LlmService,
        messages: MessageRepository, users: Arc<dyn UserRepo>,
        channels: Arc<dyn ChannelRepo>, spaces: Arc<dyn SpaceRepo>,
    ) -> Self {
        Self {
            channel_repo: channels,
            spaces,
            llm,
            users,
            messages,
            buffer_size,
            channels: Arc::new(Mutex::new(std::collections::HashMap::new())),
        }
    }

    pub async fn get_accessible_channels(&self, uid: i64) -> ApiResult<Vec<SpaceDto>> {
        // let spaces = self.spaces.get_by_member(uid).await?;
        // TODO! UNCOMMENT THIS ^^^^^^
        let spaces = self.spaces.get_all().await?;

        let mut result = Vec::new();
        for space in spaces {
            let channels = self.channel_repo.get_by_space_id(space.id).await?;
            result.push(SpaceDto {
                channels,
                id: space.id,
                owner_id: space.owner_id,
                name: space.name,
                description: space.description,
                created_at: space.created_at,
                updated_at: space.updated_at,
            });
        }

        Ok(result)
    }

    pub async fn fetch_latest_messages_desc(&self, channel_id: i64, limit: usize) -> ApiResult<Vec<ChatMsg>> {
        const PAGE: usize = 0;

        let messages = self.messages.get_latest_by_channel_desc(channel_id, limit, PAGE).await?;
        Ok(messages)
    }

    /// Sends a chat message to the specified channel, persists it to the database,
    /// and handles potential AI-generated replies asynchronously.
    ///
    /// # Parameters
    /// - `channel_id`: The ID of the channel to which the message will be sent.
    /// - `uid`: The user ID of the sender.
    /// - `text`: The content of the message to be sent.
    /// - `created_at`: The timestamp at which the message was created.
    ///
    /// # Returns
    /// - `ApiResult<()>`: Indicates success or failure of the operation.
    ///
    /// # Behavior
    /// 1. Fetches the user by their `uid`. Returns an error if the user is not found.
    /// 2. Constructs a `ChatMsg` object with the sender's `display_name` or `username`,
    ///    and the specified message content and timestamp.
    /// 3. Publishes the constructed message to the given channel.
    /// 4. Persists the message in the database.
    /// 5. Spawns an asynchronous task to generate an LLM-powered (language model) reply:
    ///    - Sends the original message to the LLM worker for a potential reply.
    ///    - Publishes the LLM's reply to the same channel if successful.
    ///    - Persists the LLM's reply to the database.
    ///
    /// # Notes
    /// - Caching with Redis is planned for both message persistence and AI replies, but
    ///   is not implemented in the current version.
    /// - The spawned asynchronous task does not block the main execution flow.
    ///
    /// # Potential Errors
    /// - Returns `AppError::NotFound` if the `uid` does not map to an existing user.
    /// - Returns an error wrapped in `ApiResult` if the database operations fail.
    ///
    /// # TODO
    /// - Implement caching for both user-supplied messages and LLM-generated replies
    ///   using Redis at the repository or service layer.
    pub async fn send(&self,
        channel_id: i64, uid: i64,
        text: &str, created_at: DateTime<Utc>
    ) -> ApiResult<()> {
        let user = self.users.get_by_id(uid).await
            .ok_or(AppError::NotFound)?;

        let message = ChatMsg {
            id: Uuid::new_v4(),
            display_name: Some(user
                .nickname.clone()
                .unwrap_or_else(|| user.username.clone())),
            user_id: uid,
            text: text.to_string(),
            timestamp: created_at,
        };

        self.publish(channel_id, message.clone()).await;
        self.messages.create_new(message.clone(), channel_id).await?;
        // TODO: caching w redis at repository layer

        if !message.text.starts_with("/ask ") {
            return Ok(());
        }


        let svc_instance = self.clone();
        if !svc_instance.llm.enabled() {
            return Ok(());
        }

        let context = self.get_history(channel_id, 25).await?;
        tokio::spawn(async move {
            let sender = match svc_instance.channels.lock().await.get(&channel_id) {
                Some(s) => s.get_sender(),
                None => return,
            };

            let response = svc_instance.llm
                .query(&message, &context, sender)
                .await;

            let Ok(reply) = response else {
                tracing::warn!("Error contacting LLM: {:?}", response);
                return;
            };

            // TODO: cache response (or do with redis!)
            if let Err(e) = svc_instance.messages.create_new(reply, channel_id).await {
                tracing::error!("Failed to persist LLM reply: {}", e);
            }

            tracing::debug!("Full LLM reply persisted");
        });

        Ok(())
    }

    async fn get_history(&self, channel_id: i64, limit: usize) -> ApiResult<Vec<ChatMsg>> {
        let mut map = self.channels.lock().await;
        if let Some(channel) = map.get(&channel_id) && !channel.cache.is_empty() {
            Ok(channel.history().clone().into_iter().take(limit).collect())
        } else {
            let messages: Vec<_> = self.messages.get_latest_by_channel_desc(channel_id, limit, 0).await?.into_iter().rev().collect();
            map.insert(channel_id,
                       ChannelState::new(self.buffer_size, Some(messages.clone().into()))
            );
            Ok(messages)
        }
    }

    /// Subscribe to the specified channel.
    pub async fn subscribe(&self, channel_id: i64) -> broadcast::Receiver<ChatEvent> {
        let mut map = self.channels.lock().await;
        let channel = map
            .entry(channel_id)
            .or_insert_with(|| ChannelState::new(self.buffer_size, None));
        channel.subscribe()
    }

    // Private helper methods

    /// Publish a message to the specified channel.
    async fn publish(&self, channel_id: i64, msg: ChatMsg) {
        let mut map = self.channels.lock().await;
        let channel = map
            .entry(channel_id)
            .or_insert_with(|| ChannelState::new(self.buffer_size, None));
        channel.send(msg);
    }
}

#[derive(Clone)]
pub struct ChannelState {
    sender: Sender<ChatEvent>,
    cache: VecDeque<ChatMsg>,
    last_updated: Instant,
}

impl ChannelState {

    const MAX_HISTORY_SIZE: usize = 100;

    #[must_use]
    pub fn new(buffer_size: usize, history: Option<VecDeque<ChatMsg>>) -> Self {
        Self {
            sender: broadcast::channel(buffer_size).0,
            cache: history.unwrap_or_default(),
            last_updated: Instant::now(),
        }
    }

    pub fn history(&self) -> &VecDeque<ChatMsg> {
        &self.cache
    }

    pub fn get_sender(&self) -> Sender<ChatEvent> {
        self.sender.clone()
    }

    #[must_use]
    pub fn send(&mut self, msg: ChatMsg) {
        while self.cache.len() >= Self::MAX_HISTORY_SIZE {
            self.cache.pop_front();
        }

        self.cache.push_back(msg.clone());

        if self.sender.send(ChatEvent::SendMessage(msg)).is_err() {
            tracing::warn!("Sent message to channel with no subscribers");
        }
    }

    #[must_use]
    pub fn subscribe(&self) -> broadcast::Receiver<ChatEvent> {
        self.sender.subscribe()
    }
}

