use crate::AppService;
use crate::error::AppError;
use chrono::{Duration, Utc};
use email::EmailMessage;
use models::WorkspaceRole;
use models::users::{user, user_email};
use models::workspaces::workspace;
use models::workspaces::workspace_membership;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceMemberInfo {
    pub user_id: Uuid,
    pub username: String,
    pub display_name: Option<String>,
    pub avatar_url: Option<String>,
    pub role: String,
    pub joined_at: chrono::DateTime<Utc>,
    /// Username of the person who invited this member.
    pub invited_by_username: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct PendingInvitationInfo {
    pub user_id: Uuid,
    pub username: String,
    pub display_name: Option<String>,
    pub avatar_url: Option<String>,
    pub email: Option<String>,
    pub role: String,
    pub invited_by_username: Option<String>,
    pub invited_at: chrono::DateTime<Utc>,
    pub expires_at: Option<chrono::DateTime<Utc>>,
}

/// Invitation received by the current user (workspace invitation for self).
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MyWorkspaceInvitation {
    pub workspace_id: Uuid,
    pub workspace_slug: String,
    pub workspace_name: String,
    pub role: String,
    pub invited_by_username: Option<String>,
    pub invited_at: chrono::DateTime<Utc>,
    pub expires_at: Option<chrono::DateTime<Utc>>,
}

/// Request body for accepting workspace invitation by slug.
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceAcceptBySlugParams {
    pub slug: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceMembersResponse {
    pub members: Vec<WorkspaceMemberInfo>,
    pub total: u64,
    pub page: u64,
    pub per_page: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceInviteParams {
    pub email: String,
    pub role: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceInviteAcceptParams {
    pub token: String,
}

impl AppService {
    pub async fn workspace_members(
        &self,
        ctx: &Session,
        workspace_slug: String,
        page: Option<u64>,
        per_page: Option<u64>,
    ) -> Result<WorkspaceMembersResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;

        // Check membership
        let _ = self
            .utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Member])
            .await;

        let page = page.unwrap_or(1);
        let per_page = per_page.unwrap_or(20);

        let memberships = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::Status.eq("active"))
            .order_by_desc(workspace_membership::Column::JoinedAt)
            .paginate(&self.db, per_page)
            .fetch_page(page - 1)
            .await?;

        let total = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::Status.eq("active"))
            .count(&self.db)
            .await?;

        let user_ids: Vec<Uuid> = memberships.iter().map(|m| m.user_id).collect();
        let users = user::Entity::find()
            .filter(user::Column::Uid.is_in(user_ids))
            .all(&self.db)
            .await?;

        // Collect invited_by user IDs
        let inviter_ids: Vec<Uuid> = memberships.iter().filter_map(|m| m.invited_by).collect();
        let inviters = if !inviter_ids.is_empty() {
            user::Entity::find()
                .filter(user::Column::Uid.is_in(inviter_ids))
                .all(&self.db)
                .await?
        } else {
            vec![]
        };

        let members: Vec<WorkspaceMemberInfo> = memberships
            .into_iter()
            .filter_map(|m| {
                let u = users.iter().find(|u| u.uid == m.user_id)?;
                let invited_by_username = m.invited_by.and_then(|uid| {
                    inviters
                        .iter()
                        .find(|i| i.uid == uid)
                        .map(|i| i.username.clone())
                });
                Some(WorkspaceMemberInfo {
                    user_id: u.uid,
                    username: u.username.clone(),
                    display_name: u.display_name.clone(),
                    avatar_url: u.avatar_url.clone(),
                    role: m.role,
                    joined_at: m.joined_at,
                    invited_by_username,
                })
            })
            .collect();

        Ok(WorkspaceMembersResponse {
            members,
            total,
            page,
            per_page,
        })
    }

    /// List pending (invited but not accepted) memberships for a workspace.
    pub async fn workspace_pending_invitations(
        &self,
        ctx: &Session,
        workspace_slug: String,
    ) -> Result<Vec<PendingInvitationInfo>, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;

        self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
            .await?;

        let pending = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::Status.eq("pending"))
            .order_by_desc(workspace_membership::Column::JoinedAt)
            .all(&self.db)
            .await?;

        let user_ids: Vec<Uuid> = pending.iter().map(|m| m.user_id).collect();
        let users = if !user_ids.is_empty() {
            user::Entity::find()
                .filter(user::Column::Uid.is_in(user_ids.clone()))
                .all(&self.db)
                .await?
        } else {
            vec![]
        };

        // Get email addresses for invited users
        let emails: Vec<(Uuid, String)> = user_email::Entity::find()
            .filter(user_email::Column::User.is_in(user_ids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|e| (e.user, e.email))
            .collect();

        // Get inviter usernames
        let inviter_ids: Vec<Uuid> = pending.iter().filter_map(|m| m.invited_by).collect();
        let inviters = if !inviter_ids.is_empty() {
            user::Entity::find()
                .filter(user::Column::Uid.is_in(inviter_ids))
                .all(&self.db)
                .await?
        } else {
            vec![]
        };

        let invitations: Vec<PendingInvitationInfo> = pending
            .into_iter()
            .filter_map(|m| {
                let u = users.iter().find(|u| u.uid == m.user_id)?;
                let email = emails
                    .iter()
                    .find(|(uid, _)| *uid == m.user_id)
                    .map(|(_, e)| e.clone());
                let invited_by_username = m.invited_by.and_then(|uid| {
                    inviters
                        .iter()
                        .find(|i| i.uid == uid)
                        .map(|i| i.username.clone())
                });
                Some(PendingInvitationInfo {
                    user_id: u.uid,
                    username: u.username.clone(),
                    display_name: u.display_name.clone(),
                    avatar_url: u.avatar_url.clone(),
                    email,
                    role: m.role,
                    invited_by_username,
                    invited_at: m.joined_at,
                    expires_at: m.invite_expires_at,
                })
            })
            .collect();

        Ok(invitations)
    }

    /// Cancel a pending invitation (remove the pending membership record).
    pub async fn workspace_cancel_invitation(
        &self,
        ctx: &Session,
        workspace_slug: String,
        target_user_id: Uuid,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;

        self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
            .await?;

        let deleted = workspace_membership::Entity::delete_many()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user_id))
            .filter(workspace_membership::Column::Status.eq("pending"))
            .exec(&self.db)
            .await?;

        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound("Invitation not found".to_string()));
        }

        Ok(())
    }

    pub async fn workspace_invite_member(
        &self,
        ctx: &Session,
        workspace_slug: String,
        params: WorkspaceInviteParams,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self
            .utils_find_workspace_by_slug(workspace_slug.clone())
            .await?;

        // Only owner/admin can invite
        self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
            .await?;

        let inviter = self.utils_find_user_by_uid(user_uid).await?;

        // Find target user by email
        let target_email = user_email::Entity::find()
            .filter(user_email::Column::Email.eq(&params.email))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        let target_user = self.utils_find_user_by_uid(target_email.user).await?;

        // Check if already a member
        if workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user.uid))
            .filter(workspace_membership::Column::Status.eq("active"))
            .one(&self.db)
            .await?
            .is_some()
        {
            return Err(AppError::BadRequest("User is already a member".to_string()));
        }

        // Generate invite token
        let token = generate_invite_token();
        let expires_at = Utc::now() + Duration::days(7);

        // Create or update pending membership
        let existing: Option<workspace_membership::Model> = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user.uid))
            .one(&self.db)
            .await?;

        let txn = self.db.begin().await?;

        match existing {
            Some(m) => {
                let mut m: workspace_membership::ActiveModel = m.into();
                m.invite_token = Set(Some(token.clone()));
                m.invite_expires_at = Set(Some(expires_at));
                m.invited_by = Set(Some(user_uid));
                m.role = Set(params
                    .role
                    .unwrap_or_else(|| WorkspaceRole::Member.to_string()));
                m.status = Set("pending".to_string());
                m.update(&txn).await?;
            }
            None => {
                let m = workspace_membership::ActiveModel {
                    id: Default::default(),
                    workspace_id: Set(ws.id),
                    user_id: Set(target_user.uid),
                    role: Set(params
                        .role
                        .unwrap_or_else(|| WorkspaceRole::Member.to_string())),
                    status: Set("pending".to_string()),
                    invited_by: Set(Some(user_uid)),
                    joined_at: Set(Utc::now()),
                    invite_token: Set(Some(token.clone())),
                    invite_expires_at: Set(Some(expires_at)),
                };
                m.insert(&txn).await?;
            }
        }

        txn.commit().await?;

        // Send invitation email
        let domain = self
            .config
            .main_domain()
            .map_err(|_| AppError::DoMainNotSet)?;

        let invite_link = format!(
            "{}/auth/accept-workspace-invite?token={}",
            domain,
            token.clone()
        );

        let envelope = EmailMessage {
            to: target_email.email.clone(),
            subject: format!("You've been invited to join {}", ws.name),
            body: format!(
                "Hello {},\n\n\
                {} has invited you to join the workspace \"{}\".\n\n\
                Click the link below to accept the invitation:\n\
                {}\n\n\
                This invitation expires in 7 days.\n\n\
                Best regards,\n\
                GitDataAI Team",
                target_user.username, inviter.username, ws.name, invite_link
            ),
        };

        self.email.send(envelope).await.map_err(|e| {
            AppError::InternalServerError(format!("Failed to send invitation email: {}", e))
        })?;

        // Send in-app notification + push notification to the invitee
        self.send_push_to_user(
            target_user.uid,
            crate::push::PushPayload {
                title: format!("Workspace invitation: {}", ws.name),
                body: format!("{} invited you to join the workspace \"{}\"", inviter.username, ws.name),
                url: Some(format!("/workspaces/{}/invitations", ws.slug)),
                icon: None,
            },
        );

        let _ = self
            .room
            .notification_create(room::NotificationCreateRequest {
                notification_type: room::NotificationType::WorkspaceInvitation,
                user_id: target_user.uid,
                title: format!("{} invited you to join \"{}\"", inviter.username, ws.name),
                content: None,
                room_id: None,
                project_id: Default::default(), // workspace invitations don't have a project_id
                related_message_id: None,
                related_user_id: Some(user_uid),
                related_room_id: None,
                metadata: Some(serde_json::json!({
                    "workspace_id": ws.id,
                    "workspace_name": ws.name,
                    "workspace_slug": ws.slug,
                    "inviter_uid": user_uid,
                })),
                expires_at: None,
            })
            .await;

        Ok(())
    }

    pub async fn workspace_accept_invitation(
        &self,
        ctx: &Session,
        params: WorkspaceInviteAcceptParams,
    ) -> Result<workspace::Model, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let membership = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::InviteToken.eq(&params.token))
            .one(&self.db)
            .await?
            .ok_or(AppError::WorkspaceInviteTokenInvalid)?;

        if membership.user_id != user_uid {
            return Err(AppError::WorkspaceInviteTokenInvalid);
        }

        if membership.status == "active" {
            return Err(AppError::WorkspaceInviteAlreadyAccepted);
        }

        if let Some(expires_at) = membership.invite_expires_at {
            if Utc::now() > expires_at {
                return Err(AppError::WorkspaceInviteExpired);
            }
        }

        let ws_id = membership.workspace_id;
        let mut m: workspace_membership::ActiveModel = membership.into();
        m.status = Set("active".to_string());
        m.invite_token = Set(None);
        m.invite_expires_at = Set(None);
        m.update(&self.db).await?;

        self.utils_find_workspace_by_id(ws_id).await
    }

    /// List all pending workspace invitations for the current user (where user is invitee).
    pub async fn workspace_my_pending_invitations(
        &self,
        ctx: &Session,
    ) -> Result<Vec<MyWorkspaceInvitation>, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let pending = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::UserId.eq(user_uid))
            .filter(workspace_membership::Column::Status.eq("pending"))
            .order_by_desc(workspace_membership::Column::JoinedAt)
            .all(&self.db)
            .await?;

        if pending.is_empty() {
            return Ok(vec![]);
        }

        // Fetch workspace info
        let ws_ids: Vec<Uuid> = pending.iter().map(|m| m.workspace_id).collect();
        let workspaces: std::collections::HashMap<Uuid, workspace::Model> = workspace::Entity::find()
            .filter(workspace::Column::Id.is_in(ws_ids.clone()))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|w| (w.id, w))
            .collect();

        // Fetch inviter usernames
        let inviter_ids: Vec<Uuid> = pending.iter().filter_map(|m| m.invited_by).collect();
        let inviters: std::collections::HashMap<Uuid, String> = if !inviter_ids.is_empty() {
            user::Entity::find()
                .filter(user::Column::Uid.is_in(inviter_ids))
                .all(&self.db)
                .await?
                .into_iter()
                .map(|u| (u.uid, u.username))
                .collect()
        } else {
            std::collections::HashMap::new()
        };

        let invitations: Vec<MyWorkspaceInvitation> = pending
            .into_iter()
            .filter_map(|m| {
                let ws = workspaces.get(&m.workspace_id)?;
                let invited_by_username = m.invited_by.and_then(|uid| inviters.get(&uid).cloned());
                Some(MyWorkspaceInvitation {
                    workspace_id: m.workspace_id,
                    workspace_slug: ws.slug.clone(),
                    workspace_name: ws.name.clone(),
                    role: m.role,
                    invited_by_username,
                    invited_at: m.joined_at,
                    expires_at: m.invite_expires_at,
                })
            })
            .collect();

        Ok(invitations)
    }

    /// Accept a workspace invitation by slug (for the current user).
    pub async fn workspace_accept_invitation_by_slug(
        &self,
        ctx: &Session,
        params: WorkspaceAcceptBySlugParams,
    ) -> Result<workspace::Model, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(params.slug.clone()).await?;

        let membership = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound(
                "No pending invitation found for this workspace".to_string(),
            ))?;

        if membership.status == "active" {
            return Err(AppError::WorkspaceInviteAlreadyAccepted);
        }

        if let Some(expires_at) = membership.invite_expires_at {
            if Utc::now() > expires_at {
                return Err(AppError::WorkspaceInviteExpired);
            }
        }

        let mut m: workspace_membership::ActiveModel = membership.into();
        m.status = Set("active".to_string());
        m.invite_token = Set(None);
        m.invite_expires_at = Set(None);
        m.update(&self.db).await?;

        Ok(ws)
    }

    pub async fn workspace_remove_member(
        &self,
        ctx: &Session,
        workspace_slug: String,
        target_user_id: Uuid,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;

        // Only owner/admin can remove members
        self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
            .await?;

        // Cannot remove owner
        let target_membership = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user_id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotWorkspaceMember)?;

        if target_membership.role == WorkspaceRole::Owner.to_string() {
            return Err(AppError::BadRequest(
                "Cannot remove workspace owner".to_string(),
            ));
        }

        workspace_membership::Entity::delete_many()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user_id))
            .exec(&self.db)
            .await?;

        Ok(())
    }

    pub async fn workspace_update_member_role(
        &self,
        ctx: &Session,
        workspace_slug: String,
        target_user_id: Uuid,
        new_role: String,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;

        self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
            .await?;

        let target_role: WorkspaceRole = new_role.parse().map_err(|_| AppError::RoleParseError)?;

        let membership = workspace_membership::Entity::find()
            .filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
            .filter(workspace_membership::Column::UserId.eq(target_user_id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotWorkspaceMember)?;

        // Cannot demote owner
        if membership.role == WorkspaceRole::Owner.to_string()
            && target_role != WorkspaceRole::Owner
        {
            return Err(AppError::BadRequest(
                "Cannot demote workspace owner".to_string(),
            ));
        }

        let mut m: workspace_membership::ActiveModel = membership.into();
        m.role = Set(new_role);
        m.update(&self.db).await?;
        Ok(())
    }
}

fn generate_invite_token() -> String {
    use rand::RngExt;
    use rand::distr::Alphanumeric;
    let token: String = rand::rng()
        .sample_iter(Alphanumeric)
        .take(64)
        .map(char::from)
        .collect();
    format!("ws_inv_{}", token)
}
