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

use super::join_answers::AnswerRequest;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct JoinRequestResponse {
    pub id: i64,
    pub project_uid: Uuid,
    pub user_uid: Uuid,
    pub username: String,
    pub status: String,
    pub message: Option<String>,
    pub processed_by: Option<Uuid>,
    pub processed_at: Option<DateTime<Utc>>,
    pub reject_reason: Option<String>,
    pub created_at: DateTime<Utc>,
}

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

#[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)]
pub struct SubmitJoinRequest {
    pub message: Option<String>,
    pub answers: Vec<AnswerRequest>,
}

#[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)]
pub struct ProcessJoinRequest {
    pub approve: bool,
    pub scope: MemberRole,
    pub reject_reason: Option<String>,
}

impl AppService {
    pub async fn project_get_join_requests(
        &self,
        project_name: String,
        status: Option<String>,
        page: Option<u64>,
        per_page: Option<u64>,
        ctx: &Session,
    ) -> Result<JoinRequestListResponse, AppError> {
        let _user_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 page = page.unwrap_or(1);
        let per_page = per_page.unwrap_or(20);

        let mut query = project_member_join_request::Entity::find()
            .filter(project_member_join_request::Column::Project.eq(project.id));

        if let Some(s) = status {
            query = query.filter(project_member_join_request::Column::Status.eq(s));
        }

        let requests = query
            .order_by_desc(project_member_join_request::Column::CreatedAt)
            .paginate(&self.db, per_page)
            .fetch_page(page - 1)
            .await?;

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

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

        let join_requests = requests
            .into_iter()
            .filter_map(|r| {
                let username = users_data
                    .iter()
                    .find(|u| u.uid == r.user)
                    .map(|u| u.username.clone())?;
                Some(JoinRequestResponse {
                    id: r.id,
                    project_uid: r.project,
                    user_uid: r.user,
                    username,
                    status: r.status,
                    message: r.message,
                    processed_by: r.processed_by,
                    processed_at: r.processed_at,
                    reject_reason: r.reject_reason,
                    created_at: r.created_at,
                })
            })
            .collect();

        Ok(JoinRequestListResponse {
            requests: join_requests,
            total,
            page,
            per_page,
        })
    }

    pub async fn project_submit_join_request(
        &self,
        project_name: String,
        request: SubmitJoinRequest,
        ctx: &Session,
    ) -> Result<i64, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .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(&self.db)
            .await?;

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

        let existing_request = project_member_join_request::Entity::find()
            .filter(project_member_join_request::Column::Project.eq(project.id))
            .filter(project_member_join_request::Column::User.eq(user_uid))
            .filter(project_member_join_request::Column::Status.eq("pending"))
            .one(&self.db)
            .await?;

        if existing_request.is_some() {
            return Err(AppError::InternalServerError(
                "Already have a pending join request".to_string(),
            ));
        }

        // Get join settings
        let settings = project_member_join_settings::Entity::find()
            .filter(project_member_join_settings::Column::Project.eq(project.id))
            .one(&self.db)
            .await?;

        // Clone message for audit log before moving
        let message = request.message.clone();

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

        let new_request = project_member_join_request::ActiveModel {
            id: Default::default(),
            project: Set(project.id),
            user: Set(user_uid),
            status: Set("pending".to_string()),
            message: Set(request.message),
            processed_by: Set(None),
            processed_at: Set(None),
            reject_reason: Set(None),
            created_at: Set(Utc::now()),
            updated_at: Set(Utc::now()),
        };
        let request_model = new_request.insert(&txn).await?;
        let request_id = request_model.id;

        // Save answers if questions are required
        if let Some(ref s) = settings {
            if s.require_questions {
                for answer in request.answers {
                    let answer_model = project_member_join_answers::ActiveModel {
                        id: Default::default(),
                        project: Set(project.id),
                        user: Set(user_uid),
                        request_id: Set(request_id),
                        question: Set(answer.question),
                        answer: Set(answer.answer),
                        created_at: Set(Utc::now()),
                    };
                    answer_model.insert(&txn).await?;
                }
            }
        }

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("submit_join_request".to_string()),
            details: Set(Some(serde_json::json!({
                "request_id": request_id,
                "message": message,
            }))),
            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: "join_request_submit".to_string(),
                    title: format!("{} requested to join the project", user_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(request_id),
                    metadata: Some(serde_json::json!({
                        "request_id": request_id,
                    })),
                    is_private: false,
                },
            )
            .await;

        Ok(request_id)
    }

    pub async fn project_process_join_request(
        &self,
        project_name: String,
        request_id: i64,
        process: ProcessJoinRequest,
        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 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 join_request = project_member_join_request::Entity::find_by_id(request_id)
            .filter(project_member_join_request::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Join request not found".to_string()))?;

        if join_request.status != "pending" {
            return Err(AppError::InternalServerError(
                "Join request already processed".to_string(),
            ));
        }

        // Clone values before moving into ActiveModel
        let request_user = join_request.user;
        let action = if process.approve {
            "approve_join_request"
        } else {
            "reject_join_request"
        };
        let reject_reason = process.reject_reason.clone();

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

        let mut active_request: project_member_join_request::ActiveModel = join_request.into();
        if process.approve {
            active_request.status = Set("approved".to_string());
            active_request.processed_by = Set(Some(user_uid));
            active_request.processed_at = Set(Some(Utc::now()));

            // Add user as member
            let member = project_members::ActiveModel {
                id: Default::default(),
                project: Set(project.id),
                user: Set(request_user),
                scope: Set(process.scope.to_string()),
                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, request_user).await;
        } else {
            active_request.status = Set("rejected".to_string());
            active_request.processed_by = Set(Some(user_uid));
            active_request.processed_at = Set(Some(Utc::now()));
            active_request.reject_reason = Set(process.reject_reason);
        }
        active_request.updated_at = Set(Utc::now());
        active_request.update(&txn).await?;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set(action.to_string()),
            details: Set(Some(serde_json::json!({
                "request_id": request_id,
                "approved": process.approve,
                "scope": process.scope.to_string(),
                "reject_reason": reject_reason,
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;

        txn.commit().await?;

        let request_username = user::Entity::find_by_id(request_user)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username)
            .unwrap_or_else(|| request_user.to_string());
        let event_type = if process.approve {
            "join_request_approve"
        } else {
            "join_request_reject"
        };
        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: event_type.to_string(),
                    title: if process.approve {
                        format!("{} approved {}'s join request", user_uid, request_username)
                    } else {
                        format!("{} rejected {}'s join request", user_uid, request_username)
                    },
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(request_id),
                    metadata: Some(serde_json::json!({
                        "request_id": request_id,
                        "approved": process.approve,
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn project_cancel_join_request(
        &self,
        project_name: String,
        request_id: i64,
        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 join_request = project_member_join_request::Entity::find_by_id(request_id)
            .filter(project_member_join_request::Column::Project.eq(project.id))
            .filter(project_member_join_request::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Join request not found".to_string()))?;

        if join_request.status != "pending" {
            return Err(AppError::InternalServerError(
                "Only pending requests can be cancelled".to_string(),
            ));
        }

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

        let mut active_request: project_member_join_request::ActiveModel = join_request.into();
        active_request.status = Set("cancelled".to_string());
        active_request.updated_at = Set(Utc::now());
        active_request.update(&txn).await?;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("cancel_join_request".to_string()),
            details: Set(Some(serde_json::json!({
                "request_id": request_id,
            }))),
            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: "join_request_cancel".to_string(),
                    title: format!("{} cancelled their join request", user_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(request_id),
                    metadata: Some(serde_json::json!({
                        "request_id": request_id,
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn project_get_my_join_requests(
        &self,
        page: Option<u64>,
        per_page: Option<u64>,
        ctx: &Session,
    ) -> Result<JoinRequestListResponse, 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 requests = project_member_join_request::Entity::find()
            .filter(project_member_join_request::Column::User.eq(user_uid))
            .order_by_desc(project_member_join_request::Column::CreatedAt)
            .paginate(&self.db, per_page)
            .fetch_page(page - 1)
            .await?;

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

        let project_ids: Vec<_> = requests.iter().map(|r| r.project).collect();
        let projects = if project_ids.is_empty() {
            vec![]
        } else {
            models::projects::project::Entity::find()
                .filter(models::projects::project::Column::Id.is_in(project_ids))
                .all(&self.db)
                .await?
        };

        let join_requests = requests
            .into_iter()
            .filter_map(|r| {
                let project_name = projects
                    .iter()
                    .find(|p| p.id == r.project)
                    .map(|p| p.name.clone())?;
                Some(JoinRequestResponse {
                    id: r.id,
                    project_uid: r.project,
                    user_uid: r.user,
                    username: project_name,
                    status: r.status,
                    message: r.message,
                    processed_by: r.processed_by,
                    processed_at: r.processed_at,
                    reject_reason: r.reject_reason,
                    created_at: r.created_at,
                })
            })
            .collect();

        Ok(JoinRequestListResponse {
            requests: join_requests,
            total,
            page,
            per_page,
        })
    }
}
