use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;

// ── Structs ───────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
    pub id: Uuid,
    pub reference_number: String,
    pub email: String,
    pub password_hash: String,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email_verified: bool,
    pub phone_verified: bool,
    pub status: String,      // ACTIVE, SUSPENDED, BANNED
    pub email_verification_token: Option<String>,
    pub email_verification_expires_at: Option<DateTime<Utc>>,
    pub reset_password_token: Option<String>,
    pub reset_password_expires_at: Option<DateTime<Utc>>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub deleted_at: Option<DateTime<Utc>>,
    pub litellm_key: Option<String>,
}


#[derive(Debug, Serialize, Deserialize)]
pub struct CreateUserPayload {
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: String,
    pub password_hash: String,
}

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct RefreshToken {
    pub id: Uuid,
    pub user_id: Uuid,
    pub token_hash: String,
    pub expires_at: DateTime<Utc>,
    pub revoked: bool,
    pub created_at: DateTime<Utc>,
}

// ── Repository ────────────────────────────────────────────────────────────────

pub struct UserRepository;

impl UserRepository {
    pub async fn create(pool: &PgPool, payload: CreateUserPayload) -> Result<User, sqlx::Error> {
        let user = sqlx::query_as::<_, User>(
            r#"
            INSERT INTO users (first_name, last_name, email, password_hash, email_verified, phone_verified)
            VALUES ($1, $2, $3, $4, false, false)
            RETURNING
                id, reference_number, email, password_hash, first_name, last_name,
                email_verified, phone_verified, status,
                email_verification_token, email_verification_expires_at,
                reset_password_token, reset_password_expires_at,
                created_at, updated_at, deleted_at, litellm_key
            "#,
        )
        .bind(&payload.first_name)
        .bind(&payload.last_name)
        .bind(payload.email.to_lowercase())
        .bind(payload.password_hash)
        .fetch_one(pool)
        .await?;

        Ok(user)
    }

    pub async fn get_by_email(pool: &PgPool, email: &str) -> Result<User, sqlx::Error> {
        sqlx::query_as::<_, User>(
            r#"
            SELECT id, reference_number, email, password_hash, first_name, last_name,
                   email_verified, phone_verified, status,
                   email_verification_token, email_verification_expires_at,
                   reset_password_token, reset_password_expires_at,
                   created_at, updated_at, deleted_at, litellm_key
            FROM users
            WHERE email = $1 AND deleted_at IS NULL
            "#,
        )
        .bind(email.to_lowercase())
        .fetch_one(pool)
        .await
    }

    pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result<User, sqlx::Error> {
        sqlx::query_as::<_, User>(
            r#"
            SELECT id, reference_number, email, password_hash, first_name, last_name,
                   email_verified, phone_verified, status,
                   email_verification_token, email_verification_expires_at,
                   reset_password_token, reset_password_expires_at,
                   created_at, updated_at, deleted_at, litellm_key
            FROM users
            WHERE id = $1 AND deleted_at IS NULL
            "#,
        )
        .bind(id)
        .fetch_one(pool)
        .await
    }

    /// Returns all approved role codes for a user, most recently assigned first (e.g. ["VIDEO_EDITOR", "JOB_SEEKER"])
    pub async fn get_user_role_keys(pool: &PgPool, user_id: Uuid) -> Result<Vec<String>, sqlx::Error> {
        let rows = sqlx::query_scalar::<_, String>(
            r#"
            SELECT r.key
            FROM user_role_assignments ur
            JOIN roles r ON ur.role_id = r.id
            WHERE ur.user_id = $1 AND ur.status = 'APPROVED'
            ORDER BY ur.created_at DESC
            "#,
        )
        .bind(user_id)
        .fetch_all(pool)
        .await?;

        Ok(rows)
    }

    pub async fn set_email_verification_token(

        pool: &PgPool,
        user_id: Uuid,
        token: &str,
        expires_at: DateTime<Utc>,
    ) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"
            UPDATE users
            SET email_verification_token = $1, email_verification_expires_at = $2, updated_at = NOW()
            WHERE id = $3
            "#,
        )
        .bind(token)
        .bind(expires_at)
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn get_by_verification_token(pool: &PgPool, token: &str) -> Result<User, sqlx::Error> {
        sqlx::query_as::<_, User>(
            r#"
            SELECT id, reference_number, email, password_hash, first_name, last_name,
                   email_verified, phone_verified, status,
                   email_verification_token, email_verification_expires_at,
                   reset_password_token, reset_password_expires_at,
                   created_at, updated_at, deleted_at, litellm_key
            FROM users
            WHERE email_verification_token = $1 AND deleted_at IS NULL
            "#,
        )
        .bind(token)
        .fetch_one(pool)
        .await
    }

    pub async fn set_email_verified(pool: &PgPool, user_id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE users SET email_verified = true, email_verification_token = NULL, email_verification_expires_at = NULL, updated_at = NOW() WHERE id = $1",
        )
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn set_reset_token(
        pool: &PgPool,
        user_id: Uuid,
        token: &str,
        expires_at: DateTime<Utc>,
    ) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"
            UPDATE users
            SET reset_password_token = $1, reset_password_expires_at = $2, updated_at = NOW()
            WHERE id = $3
            "#,
        )
        .bind(token)
        .bind(expires_at)
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn get_by_reset_token(pool: &PgPool, token: &str) -> Result<User, sqlx::Error> {
        sqlx::query_as::<_, User>(
            r#"
            SELECT id, reference_number, email, password_hash, first_name, last_name,
                   email_verified, phone_verified, status,
                   email_verification_token, email_verification_expires_at,
                   reset_password_token, reset_password_expires_at,
                   created_at, updated_at, deleted_at, litellm_key
            FROM users
            WHERE reset_password_token = $1 AND deleted_at IS NULL
            "#,
        )
        .bind(token)
        .fetch_one(pool)
        .await
    }

    pub async fn clear_reset_token(pool: &PgPool, user_id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE users SET reset_password_token = NULL, reset_password_expires_at = NULL, updated_at = NOW() WHERE id = $1",
        )
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn update_password(pool: &PgPool, user_id: Uuid, password_hash: &str) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2",
        )
        .bind(password_hash)
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn update_status(pool: &PgPool, user_id: Uuid, status: &str) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE users SET status = $1, updated_at = NOW() WHERE id = $2",
        )
        .bind(status)
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn set_litellm_key(pool: &PgPool, user_id: Uuid, key: &str) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE users SET litellm_key = $1, updated_at = NOW() WHERE id = $2",
        )
        .bind(key)
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn get_litellm_key(pool: &PgPool, user_id: Uuid) -> Result<Option<String>, sqlx::Error> {
        sqlx::query_scalar::<_, Option<String>>(
            "SELECT litellm_key FROM users WHERE id = $1 AND deleted_at IS NULL",
        )
        .bind(user_id)
        .fetch_one(pool)
        .await
    }

    pub async fn store_refresh_token(
        pool: &PgPool,
        user_id: Uuid,
        token_hash: &str,
        expires_at: DateTime<Utc>,
    ) -> Result<RefreshToken, sqlx::Error> {
        sqlx::query_as::<_, RefreshToken>(
            r#"
            INSERT INTO refresh_tokens (user_id, token_hash, expires_at)
            VALUES ($1, $2, $3)
            RETURNING id, user_id, token_hash, expires_at, revoked, created_at
            "#,
        )
        .bind(user_id)
        .bind(token_hash)
        .bind(expires_at)
        .fetch_one(pool)
        .await
    }

    pub async fn get_valid_refresh_token(
        pool: &PgPool,
        token_hash: &str,
    ) -> Result<RefreshToken, sqlx::Error> {
        sqlx::query_as::<_, RefreshToken>(
            r#"
            SELECT id, user_id, token_hash, expires_at, revoked, created_at
            FROM refresh_tokens
            WHERE token_hash = $1
              AND revoked = false
              AND expires_at > NOW()
            "#,
        )
        .bind(token_hash)
        .fetch_one(pool)
        .await
    }

    pub async fn revoke_refresh_token(pool: &PgPool, token_hash: &str) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE refresh_tokens SET revoked = true WHERE token_hash = $1",
        )
        .bind(token_hash)
        .execute(pool)
        .await?;
        Ok(())
    }

    pub async fn revoke_all_for_user(pool: &PgPool, user_id: Uuid) -> Result<(), sqlx::Error> {
        sqlx::query(
            "UPDATE refresh_tokens SET revoked = true WHERE user_id = $1",
        )
        .bind(user_id)
        .execute(pool)
        .await?;
        Ok(())
    }
}
