use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use models::projects::{
    project, MemberRole, project_audit_log, project_member_invitations, project_members,
};
use models::users::{user, user_email};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct InvitationResponse {
    pub project_uid: Uuid,
    pub project_name: String,
    pub user_uid: Uuid,
    pub invited_by: Uuid,
    pub invited_by_username: Option<String>,
    pub scope: String,
    pub accepted: bool,
    pub accepted_at: Option<DateTime<Utc>>,
    pub rejected: bool,
    pub rejected_at: Option<DateTime<Utc>>,
    pub created_at: DateTime<Utc>,
}

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

impl InvitationResponse {
    pub async fn from_model(
        inv: project_member_invitations::Model,
        db: &DatabaseConnection,
    ) -> Self {
        let project_name = project::Entity::find_by_id(inv.project)
            .one(db)
            .await
            .ok()
            .flatten()
            .map(|p| p.name)
            .unwrap_or_default();
        let invited_by_username = user::Entity::find_by_id(inv.invited_by)
            .one(db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username);
        InvitationResponse {
            project_uid: inv.project,
            project_name,
            user_uid: inv.user,
            invited_by: inv.invited_by,
            invited_by_username,
            scope: inv.scope,
            accepted: inv.accepted,
            accepted_at: inv.accepted_at,
            rejected: inv.rejected,
            rejected_at: inv.rejected_at,
            created_at: inv.created_at,
        }
    }
}

impl AppService {
    pub async fn project_get_invitations(
        &self,
        project_name: String,
        page: Option<u64>,
        per_page: Option<u64>,
        ctx: &Session,
    ) -> Result<InvitationListResponse, AppError> {
        let _user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let role = self
            .utils_project_context_role(&ctx, project.name.clone())
            .await
            .map_err(|_| AppError::NoPower)?;

        if role != MemberRole::Owner && role != MemberRole::Admin {
            return Err(AppError::NoPower);
        }

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

        let invitations = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .order_by_desc(project_member_invitations::Column::CreatedAt)
            .paginate(&self.db, per_page)
            .fetch_page(page - 1)
            .await?;

        let total = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .count(&self.db)
            .await?;

        let project_name = project.name.clone();
        let inviter_ids: Vec<Uuid> = invitations.iter().map(|i| i.invited_by).collect();
        let inviters: std::collections::HashMap<Uuid, Option<String>> = user::Entity::find()
            .filter(user::Column::Uid.is_in(inviter_ids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|u| (u.uid, Some(u.username)))
            .collect();

        let invitations = invitations
            .into_iter()
            .map(|inv| InvitationResponse {
                project_uid: inv.project,
                project_name: project_name.clone(),
                user_uid: inv.user,
                invited_by: inv.invited_by,
                invited_by_username: inviters.get(&inv.invited_by).cloned().flatten(),
                scope: inv.scope,
                accepted: inv.accepted,
                accepted_at: inv.accepted_at,
                rejected: inv.rejected,
                rejected_at: inv.rejected_at,
                created_at: inv.created_at,
            })
            .collect();

        Ok(InvitationListResponse {
            invitations,
            total,
            page,
            per_page,
        })
    }

    pub async fn project_get_my_invitations(
        &self,
        page: Option<u64>,
        per_page: Option<u64>,
        ctx: &Session,
    ) -> Result<InvitationListResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

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

        let invitations = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::User.eq(user_uid))
            .filter(project_member_invitations::Column::Accepted.eq(false))
            .filter(project_member_invitations::Column::Rejected.eq(false))
            .order_by_desc(project_member_invitations::Column::CreatedAt)
            .paginate(&self.db, per_page)
            .fetch_page(page - 1)
            .await?;

        let total = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::User.eq(user_uid))
            .filter(project_member_invitations::Column::Accepted.eq(false))
            .filter(project_member_invitations::Column::Rejected.eq(false))
            .count(&self.db)
            .await?;

        let invitations = futures::future::join_all(
            invitations
                .into_iter()
                .map(|inv| InvitationResponse::from_model(inv, self.db.writer())),
        )
        .await
        .into_iter()
        .collect();

        Ok(InvitationListResponse {
            invitations,
            total,
            page,
            per_page,
        })
    }

    pub async fn project_invite_user(
        &self,
        project_name: String,
        invitee_email: String,
        scope: MemberRole,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let inviter_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;

        let role = self
            .utils_project_context_role(&ctx, project_name.clone())
            .await
            .map_err(|_| AppError::NoPower)?;

        if role != MemberRole::Owner && role != MemberRole::Admin {
            return Err(AppError::NoPower);
        }
        let target_user = user_email::Entity::find()
            .filter(user_email::Column::Email.eq(invitee_email.clone()))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;
        let user = user::Entity::find_by_id(target_user.user)
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;
        let target_uid = user.uid;

        let existing_member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(target_uid))
            .one(&self.db)
            .await?;

        if existing_member.is_some() {
            return Err(AppError::InternalServerError(
                "User is already a member of this project".to_string(),
            ));
        }

        let existing_invitation = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .filter(project_member_invitations::Column::User.eq(target_uid))
            .filter(project_member_invitations::Column::Accepted.eq(false))
            .filter(project_member_invitations::Column::Rejected.eq(false))
            .one(&self.db)
            .await?;

        if existing_invitation.is_some() {
            return Err(AppError::InternalServerError(
                "An invitation already exists for this user".to_string(),
            ));
        }

        let txn = self.db.begin().await.map_err(|_| AppError::InternalError)?;

        let invitation = project_member_invitations::ActiveModel {
            id: Default::default(),
            project: Set(project.id),
            user: Set(target_uid),
            invited_by: Set(inviter_uid),
            scope: Set(scope.clone().to_string()),
            accepted: Set(false),
            accepted_at: Set(None),
            rejected: Set(false),
            rejected_at: Set(None),
            created_at: Set(Utc::now()),
        };
        invitation.insert(&txn).await?;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(inviter_uid),
            action: Set("invite_user".to_string()),
            details: Set(Some(serde_json::json!({
                "invitee_uid": target_uid,
                "scope": format!("{:?}", scope),
                "project_name": project.name.clone(),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;

        txn.commit().await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                inviter_uid,
                super::activity::ActivityLogParams {
                    event_type: "member_invite".to_string(),
                    title: format!("{} invited {} to the project", inviter_uid, user.username),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "invitee_uid": target_uid,
                        "invitee_username": user.username,
                        "scope": format!("{:?}", scope),
                    })),
                    is_private: false,
                },
            )
            .await;

        let inviter = user::Entity::find_by_id(inviter_uid)
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        let envelope = queue::EmailEnvelope {
            id: Uuid::new_v4(),
            to: invitee_email,
            subject: format!("You've been invited to join project: {}", project.name),
            body: format!(
                "Hello {},\n\n\
                {} has invited you to join the project \"{}\" with the role of {:?}.\n\n\
                Please log in to your account to accept or decline this invitation.\n\n\
                Project: {}\n\
                Role: {:?}\n\
                Invited by: {}\n\n\
                Best regards,\n\
                GitDataAI Team",
                user.username,
                inviter.username,
                project.name,
                scope,
                project.name,
                scope,
                inviter.username
            ),
            created_at: chrono::Utc::now(),
        };

        if let Err(_e) = self.queue_producer.publish_email(envelope).await {
            // Failed to queue invitation email
        }

        // Send in-app notification + push notification to the invitee
        self.send_push_to_user(
            target_uid,
            crate::push::PushPayload {
                title: format!("Project invitation: {}", project.name),
                body: format!("{} invited you to join \"{}\" as {:?}", inviter.username, project.name, scope),
                url: Some(format!("/projects/{}/invitations", project.name)),
                icon: None,
            },
        );

        let _ = self
            .room
            .notification_create(room::NotificationCreateRequest {
                notification_type: room::NotificationType::ProjectInvitation,
                user_id: target_uid,
                title: format!("{} invited you to join \"{}\"", inviter.username, project.name),
                content: Some(format!("Role: {:?}", scope)),
                room_id: None,
                project_id: project.id,
                related_message_id: None,
                related_user_id: Some(inviter_uid),
                related_room_id: None,
                metadata: Some(serde_json::json!({
                    "project_id": project.id,
                    "project_name": project.name,
                    "inviter_uid": inviter_uid,
                    "scope": format!("{:?}", scope),
                })),
                expires_at: None,
            })
            .await;

        Ok(())
    }

    pub async fn project_accept_invitation(
        &self,
        project_name: String,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        let invitation = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .filter(project_member_invitations::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound(
                "No pending invitation found for this project".to_string(),
            ))?;
        if invitation.accepted || invitation.rejected {
            return Err(AppError::InternalServerError(
                "Invitation already processed".to_string(),
            ));
        }
        let txn = self.db.begin().await.map_err(|_| AppError::InternalError)?;
        let mut active_invitation: project_member_invitations::ActiveModel =
            invitation.clone().into();
        active_invitation.accepted = Set(true);
        active_invitation.accepted_at = Set(Some(Utc::now()));
        active_invitation.update(&txn).await?;
        let existing_member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&txn)
            .await?;
        if existing_member.is_none() {
            let member = project_members::ActiveModel {
                id: Default::default(),
                project: Set(project.id),
                user: Set(user_uid),
                scope: Set(invitation.scope.clone()),
                joined_at: Set(Utc::now()),
            };
            member.insert(&txn).await?;

            // Auto-add user to all rooms in the project
            let _ = Self::add_user_to_all_project_rooms(&txn, project.id, user_uid).await;
        }
        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("accept_invitation".to_string()),
            details: Set(Some(serde_json::json!({
                "project_name": project.name.clone(),
                "scope": format!("{:?}", invitation.scope),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;
        txn.commit().await?;

        let actor_username = user::Entity::find_by_id(user_uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username)
            .unwrap_or_else(|| user_uid.to_string());
        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "member_join".to_string(),
                    title: format!("{} joined the project", actor_username),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "scope": format!("{:?}", invitation.scope),
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn project_reject_invitation(
        &self,
        project_name: String,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        let invitation = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .filter(project_member_invitations::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound(
                "No pending invitation found for this project".to_string(),
            ))?;
        if invitation.accepted || invitation.rejected {
            return Err(AppError::InternalServerError(
                "Invitation already processed".to_string(),
            ));
        }
        let txn = self.db.begin().await.map_err(|_| AppError::InternalError)?;
        let mut active_invitation: project_member_invitations::ActiveModel = invitation.into();
        active_invitation.rejected = Set(true);
        active_invitation.rejected_at = Set(Some(Utc::now()));
        active_invitation.update(&txn).await?;
        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("reject_invitation".to_string()),
            details: Set(Some(serde_json::json!({
                "project_name": project.name.clone(),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;
        txn.commit().await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "invitation_rejected".to_string(),
                    title: format!("{} rejected invitation to join the project", user_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "project_name": project.name.clone(),
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn project_cancel_invitation(
        &self,
        project_name: String,
        invitee_uid: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        let invitation = project_member_invitations::Entity::find()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .filter(project_member_invitations::Column::User.eq(invitee_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound(
                "No invitation found for this user".to_string(),
            ))?;
        let role = self
            .utils_project_context_role(&ctx, project_name.clone())
            .await
            .map_err(|_| AppError::NoPower)?;
        if role != MemberRole::Owner
            && role != MemberRole::Admin
            && invitation.invited_by != user_uid
        {
            return Err(AppError::NoPower);
        }
        let txn = self.db.begin().await.map_err(|_| AppError::InternalError)?;
        project_member_invitations::Entity::delete_many()
            .filter(project_member_invitations::Column::Project.eq(project.id))
            .filter(project_member_invitations::Column::User.eq(invitee_uid))
            .exec(&txn)
            .await?;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("cancel_invitation".to_string()),
            details: Set(Some(serde_json::json!({
                "invitee_uid": invitee_uid,
                "project_name": project.name.clone(),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;
        txn.commit().await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "invitation_cancelled".to_string(),
                    title: format!("{} cancelled invitation for user {}", user_uid, invitee_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "invitee_uid": invitee_uid,
                        "project_name": project.name.clone(),
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }
}
