use axum::http::StatusCode;
use sha2::{Digest, Sha256};
use sqlx::PgPool;
use uuid::Uuid;

use crate::auth::{create_token, Claims};
use crate::repositories;
use crate::repositories::session_repo;

pub struct LoginResponse {
    pub token: String,
    pub user_id: Uuid,
    pub username: String,
    pub role: String,
}

pub async fn login(
    pool: &PgPool,
    username: &str,
    password: &str,
    ip_address: Option<&str>,
    user_agent: Option<&str>,
    jwt_secret: &str,
    jwt_expiry_hours: i64,
) -> Result<LoginResponse, StatusCode> {
    let user = repositories::user_repo::find_by_username(pool, username)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
        .ok_or(StatusCode::UNAUTHORIZED)?;

    if !user.is_active {
        return Err(StatusCode::FORBIDDEN);
    }

    if user.is_locked {
        return Err(StatusCode::LOCKED);
    }

    let password_valid = bcrypt::verify(password, &user.password_hash)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    if !password_valid {
        let _ = repositories::user_repo::increment_failed_login(pool, username).await;
        return Err(StatusCode::UNAUTHORIZED);
    }

    let _ = repositories::user_repo::update_last_login(pool, user.id, ip_address.unwrap_or("")).await;

    let role = "USER";

    let token = create_token(user.id, &user.username, role, jwt_secret, jwt_expiry_hours)?;

    let token_hash = hex::encode(Sha256::digest(token.as_bytes()));

    let expires_at = chrono::Utc::now()
        .checked_add_signed(chrono::Duration::hours(jwt_expiry_hours))
        .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

    let _ = session_repo::create_session(
        pool,
        user.id,
        &token_hash,
        ip_address,
        user_agent,
        &expires_at,
    )
    .await;

    Ok(LoginResponse {
        token,
        user_id: user.id,
        username: user.username,
        role: role.to_string(),
    })
}

pub async fn logout(pool: &PgPool, token: &str) -> Result<(), StatusCode> {
    let token_hash = hex::encode(Sha256::digest(token.as_bytes()));
    session_repo::invalidate_session(pool, &token_hash)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}

pub fn validate_token(token: &str, jwt_secret: &str) -> Result<Claims, StatusCode> {
    crate::auth::validate_token(token, jwt_secret)
}
