use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::projects::{project, project_members};
use models::users::user;
use sea_orm::prelude::*;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserProjectInfo {
    pub uid: Uuid,
    pub name: String,
    pub display_name: String,
    pub avatar_url: Option<String>,
    pub description: Option<String>,
    pub is_public: bool,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
    pub member_count: i64,
    pub is_member: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserProjectsResponse {
    pub username: String,
    pub projects: Vec<UserProjectInfo>,
    pub total_count: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
pub struct UserProjectsQuery {
    pub page: Option<u64>,
    pub per_page: Option<u64>,
}

impl AppService {
    pub async fn get_user_projects(
        &self,
        context: Session,
        username: String,
        query: UserProjectsQuery,
    ) -> Result<UserProjectsResponse, AppError> {
        let target_user = user::Entity::find()
            .filter(user::Column::Username.eq(&username))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        let current_user_uid = context.user();

        let is_owner = current_user_uid
            .map(|uid| uid == target_user.uid)
            .unwrap_or(false);
        let has_admin_privilege = false;

        let page = std::cmp::Ord::max(query.page.unwrap_or(1), 1);
        let per_page = std::cmp::Ord::min(std::cmp::Ord::max(query.per_page.unwrap_or(20), 1), 100);
        let offset = (page - 1) * per_page;

        // Projects where user is the creator
        let created_projects: Vec<Uuid> = project::Entity::find()
            .filter(project::Column::CreatedBy.eq(target_user.uid))
            .select_only()
            .column(project::Column::Id)
            .into_tuple::<Uuid>()
            .all(&self.db)
            .await?;

        // Projects where user is a member (via invitation)
        let member_projects: Vec<Uuid> = project_members::Entity::find()
            .filter(project_members::Column::User.eq(target_user.uid))
            .select_only()
            .column(project_members::Column::Project)
            .into_tuple::<Uuid>()
            .all(&self.db)
            .await?;

        // Union + dedup (preserving first occurrence order)
        let mut project_ids: Vec<Uuid> = created_projects;
        let new_ids: Vec<Uuid> = member_projects.into_iter().filter(|id| !project_ids.contains(id)).collect();
        project_ids.extend(new_ids);

        let total_count = project_ids.len() as u64;

        // Paginate
        let page_ids: Vec<Uuid> = project_ids.into_iter().skip(offset as usize).take(per_page as usize).collect();

        if page_ids.is_empty() {
            return Ok(UserProjectsResponse {
                username: target_user.username,
                projects: vec![],
                total_count,
            });
        }

        let project_list: Vec<project::Model> = project::Entity::find()
            .filter(project::Column::Id.is_in(page_ids.clone()))
            .all(&self.db)
            .await?;

        // Preserve the order from project_ids (created projects first, then member projects)
        let mut sorted_projects = project_list;
        sorted_projects.sort_by(|a, b| {
            let a_idx = page_ids.iter().position(|&x| x == a.id).unwrap_or(usize::MAX);
            let b_idx = page_ids.iter().position(|&x| x == b.id).unwrap_or(usize::MAX);
            a_idx.cmp(&b_idx)
        });

        let user_project_memberships: std::collections::HashSet<Uuid> =
            if let Some(uid) = current_user_uid {
                project_members::Entity::find()
                    .filter(project_members::Column::User.eq(uid))
                    .select_only()
                    .column(project_members::Column::Project)
                    .into_tuple::<Uuid>()
                    .all(&self.db)
                    .await?
                    .into_iter()
                    .collect()
            } else {
                std::collections::HashSet::new()
            };

        let mut project_infos: Vec<UserProjectInfo> = Vec::new();
        for project in sorted_projects {
            // Privacy: non-owners/non-admins only see public projects (member or created)
            if !is_owner && !has_admin_privilege && !project.is_public {
                continue;
            }
            let member_count = project_members::Entity::find()
                .filter(project_members::Column::Project.eq(project.id))
                .count(&self.db)
                .await?;

            let is_member = user_project_memberships.contains(&project.id);

            project_infos.push(UserProjectInfo {
                uid: project.id,
                name: project.name,
                display_name: project.display_name,
                avatar_url: project.avatar_url,
                description: project.description,
                is_public: project.is_public,
                created_at: project.created_at,
                updated_at: project.updated_at,
                member_count: member_count as i64,
                is_member,
            });
        }

        Ok(UserProjectsResponse {
            username: target_user.username,
            projects: project_infos,
            total_count,
        })
    }

    pub async fn get_current_user_projects(
        &self,
        context: Session,
        query: UserProjectsQuery,
    ) -> Result<UserProjectsResponse, AppError> {
        let user_uid = context.user().ok_or(AppError::Unauthorized)?;

        let user = user::Entity::find()
            .filter(user::Column::Uid.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        self.get_user_projects(context, user.username, query).await
    }
}
