use actix_web::{HttpResponse, Responder, Scope, get, post, web};
use rand::{Rng, distr::Alphanumeric};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::app_data::AppData;
use crate::entity::{invite, user};
use crate::error::ApiError;
use crate::middleware::auth::AuthUser;

#[derive(Debug, Deserialize)]
pub struct CreateInviteRequest {
    pub max_uses: Option<i32>,
    pub expires_in_days: Option<i32>,
}

#[derive(Debug, Serialize)]
pub struct OkResponse {
    pub ok: bool,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisteredUserInfo {
    pub id: Uuid,
    pub username: String,
    pub avatar: Option<String>,
    pub created_at: chrono::DateTime<chrono::FixedOffset>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InviteWithUsers {
    pub id: Uuid,
    pub code: String,
    pub creator_id: Uuid,
    pub max_uses: i32,
    pub used_count: i32,
    pub expires_at: chrono::DateTime<chrono::FixedOffset>,
    pub revoked_at: Option<chrono::DateTime<chrono::FixedOffset>>,
    pub created_at: chrono::DateTime<chrono::FixedOffset>,
    pub registered_users: Vec<RegisteredUserInfo>,
}

#[post("")]
async fn create_invite(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    body: web::Json<CreateInviteRequest>,
) -> Result<impl Responder, ApiError> {
    let max_uses = body.max_uses.unwrap_or(5).clamp(1, 20);
    let expires_in_days = body.expires_in_days.unwrap_or(7).clamp(1, 30);

    let expires_at =
        chrono::Utc::now().fixed_offset() + chrono::Duration::days(expires_in_days as i64);

    let code = generate_invite_code();

    let new_invite = invite::ActiveModel {
        id: Set(Uuid::new_v4()),
        code: Set(code),
        creator_id: Set(auth.user_id),
        max_uses: Set(max_uses),
        used_count: Set(0),
        expires_at: Set(expires_at),
        revoked_at: Set(None),
        created_at: Set(chrono::Utc::now().fixed_offset()),
    };

    let result = new_invite.insert(&app_data.db).await?;

    Ok(HttpResponse::Ok().json(result))
}

#[get("")]
async fn list_invites(
    app_data: web::Data<AppData>,
    auth: AuthUser,
) -> Result<impl Responder, ApiError> {
    let invites = invite::Entity::find()
        .filter(invite::Column::CreatorId.eq(auth.user_id))
        .order_by_desc(invite::Column::CreatedAt)
        .all(&app_data.db)
        .await?;

    Ok(HttpResponse::Ok().json(invites))
}

#[get("/{id}")]
async fn get_invite(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    let invite = invite::Entity::find_by_id(id)
        .filter(invite::Column::CreatorId.eq(auth.user_id))
        .one(&app_data.db)
        .await?
        .ok_or_else(|| ApiError::NotFound("Invite not found".to_string()))?;

    // Get users who registered with this invite
    let registered_users = user::Entity::find()
        .filter(user::Column::InviteId.eq(invite.id))
        .order_by_asc(user::Column::CreatedAt)
        .all(&app_data.db)
        .await?;

    let registered_users_info: Vec<RegisteredUserInfo> = registered_users
        .into_iter()
        .map(|u| RegisteredUserInfo {
            id: u.id,
            username: u.username,
            avatar: u.avatar,
            created_at: u.created_at,
        })
        .collect();

    Ok(HttpResponse::Ok().json(InviteWithUsers {
        id: invite.id,
        code: invite.code,
        creator_id: invite.creator_id,
        max_uses: invite.max_uses,
        used_count: invite.used_count,
        expires_at: invite.expires_at,
        revoked_at: invite.revoked_at,
        created_at: invite.created_at,
        registered_users: registered_users_info,
    }))
}

#[post("/{id}/revoke")]
async fn revoke_invite(
    app_data: web::Data<AppData>,
    auth: AuthUser,
    path: web::Path<Uuid>,
) -> Result<impl Responder, ApiError> {
    let id = path.into_inner();

    let invite = invite::Entity::find_by_id(id)
        .filter(invite::Column::CreatorId.eq(auth.user_id))
        .one(&app_data.db)
        .await?
        .ok_or_else(|| ApiError::NotFound("Invite not found".to_string()))?;

    let mut active: invite::ActiveModel = invite.into();
    active.revoked_at = Set(Some(chrono::Utc::now().fixed_offset()));
    active.update(&app_data.db).await?;

    Ok(HttpResponse::Ok().json(OkResponse { ok: true }))
}

fn generate_invite_code() -> String {
    let mut rng = rand::rng();
    let suffix: String = (0..6).map(|_| rng.sample(Alphanumeric) as char).collect();
    format!("INV-{}", suffix)
}

pub fn routes() -> Scope {
    web::scope("/api/invites")
        .service(create_invite)
        .service(list_invites)
        .service(get_invite)
        .service(revoke_invite)
}
