use chrono::Utc;
use uuid::Uuid;

use crate::event::{RoomInfo, UserInfo, WorkspaceInfo, invite};
use crate::{ChannelBus, ChannelError, ChannelResult};

use super::WsHandler;
use super::WsOutEvent;

impl WsHandler {
    pub(super) async fn invite_create(
        bus: &ChannelBus,
        user_id: Uuid,
        workspace: Uuid,
        _room: Option<Uuid>,
        _max_uses: Option<i32>,
        _expires_at: Option<chrono::DateTime<chrono::Utc>>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_workspace_member(bus, user_id, workspace).await?;
        let invite_id = Uuid::now_v7();
        let code = Uuid::now_v7().to_string();
        let id_key = format!("invite:id:{}", invite_id);
        let code_key = format!("invite:code:{}", code);
        let meta = serde_json::json!({
            "workspace": workspace,
            "created_by": user_id,
            "room": _room,
            "max_uses": _max_uses,
            "expires_at": _expires_at,
        });
        bus.inner.cache.set(&id_key, &meta.to_string()).await?;
        bus.inner
            .cache
            .set(&code_key, &invite_id.to_string())
            .await?;
        let inv_room = match _room {
            Some(r) => Some(
                bus.lookup_room(r)
                    .await
                    .unwrap_or_else(|_| RoomInfo::unknown(r)),
            ),
            None => None,
        };
        let data = invite::InviteCreatedService {
            id: invite_id,
            workspace: bus
                .lookup_workspace(workspace)
                .await
                .unwrap_or_else(|_| WorkspaceInfo::unknown(workspace)),
            room: inv_room,
            inviter: bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id)),
            invitee: None,
            code,
            max_uses: _max_uses,
            expires_at: _expires_at,
            created_at: Utc::now(),
        };
        Ok(Some(WsOutEvent::InviteCreated { data }))
    }

    pub(super) async fn invite_accept(
        bus: &ChannelBus,
        user_id: Uuid,
        code: String,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let code_key = format!("invite:code:{}", code);
        let invite_id_str: Option<String> =
            bus.inner.cache.get(&code_key).await?;
        let invite_id = invite_id_str
            .as_deref()
            .and_then(|s| Uuid::parse_str(s).ok())
            .ok_or(ChannelError::RoomNotFound)?;
        let id_key = format!("invite:id:{}", invite_id);
        let stored: Option<String> = bus.inner.cache.get(&id_key).await?;
        let meta: serde_json::Value = stored
            .as_deref()
            .and_then(|s| serde_json::from_str(s).ok())
            .ok_or(ChannelError::RoomNotFound)?;
        let wk = meta["workspace"]
            .as_str()
            .and_then(|s| Uuid::parse_str(s).ok())
            .ok_or(ChannelError::RoomNotFound)?;
        db::sqlx::query(
            "INSERT INTO wk_member (wk, \"user\", owner, admin, join_at) \
             VALUES ($1, $2, false, false, now()) \
             ON CONFLICT DO NOTHING",
        )
        .bind(wk)
        .bind(user_id)
        .execute(bus.inner.db.writer())
        .await?;
        db::sqlx::query(
            "INSERT INTO wk_apply_join (wk, \"user\", status, created_at, updated_at) \
             VALUES ($1, $2, 'accepted', now(), now())",
        )
        .bind(wk)
        .bind(user_id)
        .execute(bus.inner.db.writer())
        .await?;
        bus.inner.cache.remove(&code_key).await?;
        bus.inner.cache.remove(&id_key).await?;
        let data = invite::InviteAcceptedService {
            id: Uuid::now_v7(),
            workspace: bus
                .lookup_workspace(wk)
                .await
                .unwrap_or_else(|_| WorkspaceInfo::unknown(wk)),
            room: None,
            user: bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id)),
            accepted_at: Utc::now(),
        };
        bus.workspace_changed(wk).await?;
        Ok(Some(WsOutEvent::InviteAccepted { data }))
    }

    pub(super) async fn invite_revoke(
        bus: &ChannelBus,
        _user_id: Uuid,
        id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let id_key = format!("invite:id:{}", id);
        let stored: Option<String> = bus.inner.cache.get(&id_key).await?;
        let meta: serde_json::Value = stored
            .as_deref()
            .and_then(|s| serde_json::from_str(s).ok())
            .ok_or(ChannelError::RoomNotFound)?;
        let created_by = meta["created_by"]
            .as_str()
            .and_then(|s| Uuid::parse_str(s).ok())
            .ok_or(ChannelError::RoomNotFound)?;
        if created_by != _user_id {
            return Err(ChannelError::AccessDenied);
        }
        bus.inner.cache.remove(&id_key).await?;
        Ok(None)
    }
}
