use actix_web::{HttpResponse, Responder, Scope, post, web};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};
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::create_token;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterRequest {
    pub username: String,
    pub password: String,
    pub invite_code: String,
}

#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user: UserInfo,
}

#[derive(Debug, Serialize)]
pub struct UserInfo {
    pub id: Uuid,
    pub username: String,
}

#[post("/register")]
async fn register(
    app_data: web::Data<AppData>,
    body: web::Json<RegisterRequest>,
) -> Result<impl Responder, ApiError> {
    if body.username.len() < 3 {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }
    if body.password.len() < 6 {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }
    if body.invite_code.len() < 4 {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }

    let now = chrono::Utc::now().fixed_offset();

    let result = app_data
        .db
        .transaction::<_, (Uuid, String), ApiError>(|txn| {
            let username = body.username.clone();
            let password = body.password.clone();
            let invite_code = body.invite_code.clone();
            Box::pin(async move {
                // Find valid invite
                let inv = invite::Entity::find()
                    .filter(invite::Column::Code.eq(&invite_code))
                    .filter(invite::Column::RevokedAt.is_null())
                    .filter(invite::Column::ExpiresAt.gt(now))
                    .one(txn)
                    .await?
                    .ok_or_else(|| ApiError::BadRequest("Invalid invite".to_string()))?;

                if inv.used_count >= inv.max_uses {
                    return Err(ApiError::BadRequest("Invalid invite".to_string()));
                }

                // Check username exists
                let existing = user::Entity::find()
                    .filter(user::Column::Username.eq(&username))
                    .one(txn)
                    .await?;

                if existing.is_some() {
                    return Err(ApiError::Conflict("Username taken".to_string()));
                }

                // Create user
                let password_hash = bcrypt::hash(&password, 10)?;
                let user_id = Uuid::new_v4();
                let invite_id = inv.id;

                let new_user = user::ActiveModel {
                    id: Set(user_id),
                    username: Set(username.clone()),
                    password_hash: Set(password_hash),
                    avatar: Set(None),
                    timezone: Set("Asia/Shanghai".to_string()),
                    bark_url: Set(None),
                    inapp_enabled: Set(true),
                    bark_enabled: Set(false),
                    invite_id: Set(Some(invite_id)),
                    created_at: Set(now),
                    updated_at: Set(now),
                };
                new_user.insert(txn).await?;

                // Update invite used count
                let mut inv_active: invite::ActiveModel = inv.into();
                inv_active.used_count = Set(inv_active.used_count.unwrap() + 1);
                inv_active.update(txn).await?;

                Ok((user_id, username))
            })
        })
        .await
        .map_err(|e| match e {
            sea_orm::TransactionError::Connection(e) => ApiError::Internal(e.to_string()),
            sea_orm::TransactionError::Transaction(e) => e,
        })?;

    let token = create_token(result.0, &app_data.jwt_secret)?;

    Ok(HttpResponse::Ok().json(AuthResponse {
        token,
        user: UserInfo {
            id: result.0,
            username: result.1,
        },
    }))
}

#[post("/login")]
async fn login(
    app_data: web::Data<AppData>,
    body: web::Json<LoginRequest>,
) -> Result<impl Responder, ApiError> {
    if body.username.len() < 3 || body.password.len() < 6 {
        return Err(ApiError::BadRequest("Invalid payload".to_string()));
    }

    let user = user::Entity::find()
        .filter(user::Column::Username.eq(&body.username))
        .one(&app_data.db)
        .await?
        .ok_or_else(|| ApiError::Unauthorized("Invalid credentials".to_string()))?;

    let valid = bcrypt::verify(&body.password, &user.password_hash)?;
    if !valid {
        return Err(ApiError::Unauthorized("Invalid credentials".to_string()));
    }

    let token = create_token(user.id, &app_data.jwt_secret)?;

    Ok(HttpResponse::Ok().json(AuthResponse {
        token,
        user: UserInfo {
            id: user.id,
            username: user.username,
        },
    }))
}

pub fn routes() -> Scope {
    web::scope("/api/auth").service(register).service(login)
}
