use std::sync::Arc;

use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};
use tracing::{info, warn};
use uuid::Uuid;

use crate::error::{auth, http, ControlCenterError, Result};
use crate::models::{
    ClientInfo, LoginRequest, LogoutRequest, RefreshTokenRequest, Session, TokenResponse,
};
use crate::services::{DatabaseService, JwtService, UserService};

/// Authentication service for handling login, logout, and token management
#[derive(Clone)]
pub struct AuthService {
    db: Arc<DatabaseService>,
    jwt_service: Arc<JwtService>,
    user_service: Arc<UserService>,
}

impl AuthService {
    /// Create a new authentication service
    pub fn new(
        db: Arc<DatabaseService>,
        jwt_service: Arc<JwtService>,
        user_service: Arc<UserService>,
    ) -> Self {
        Self {
            db,
            jwt_service,
            user_service,
        }
    }

    /// Authenticate user with email/username and password
    pub async fn login(
        &self,
        request: LoginRequest,
        client_info: Option<ClientInfo>,
    ) -> Result<TokenResponse> {
        // Find user by email or username
        let user = self
            .user_service
            .find_by_email_or_username(&request.username_or_email)
            .await?
            .ok_or_else(|| {
                ControlCenterError::Auth(auth::AuthError::Authentication(
                    "Invalid credentials".to_string(),
                ))
            })?;

        // Check if user is active
        if !user.is_active {
            return Err(ControlCenterError::Auth(auth::AuthError::Authentication(
                "Account is disabled".to_string(),
            )));
        }

        // Verify password
        if !self.verify_password(&request.password, &user.password_hash)? {
            warn!("Failed login attempt for user: {}", user.username);
            return Err(ControlCenterError::Auth(auth::AuthError::Authentication(
                "Invalid credentials".to_string(),
            )));
        }

        // Update user's last login
        self.user_service.update_last_login(user.user_id).await?;

        // Create session
        let session = self.create_session(user.user_id, client_info).await?;

        // Generate token pair
        let token_response = self.jwt_service.generate_token_pair(
            user.user_id,
            session.session_id,
            user.roles.clone(),
        )?;

        info!("User {} logged in successfully", user.username);

        Ok(token_response)
    }

    /// Refresh access token using refresh token
    pub async fn refresh_token(&self, request: RefreshTokenRequest) -> Result<TokenResponse> {
        // Verify refresh token
        let token_data = self
            .jwt_service
            .verify_refresh_token(&request.refresh_token)?;

        let user_id = Uuid::parse_str(&token_data.claims.sub).map_err(|_| {
            ControlCenterError::Auth(auth::AuthError::Token(
                "Invalid user ID in token".to_string(),
            ))
        })?;

        let session_id = Uuid::parse_str(&token_data.claims.session_id).map_err(|_| {
            ControlCenterError::Auth(auth::AuthError::Token(
                "Invalid session ID in token".to_string(),
            ))
        })?;

        // Find and validate session
        let mut session = self.get_session_by_id(session_id).await?;

        if !session.is_active {
            return Err(ControlCenterError::Auth(auth::AuthError::SessionExpired(
                "Session is inactive".to_string(),
            )));
        }

        if session.is_expired() {
            return Err(ControlCenterError::Auth(auth::AuthError::SessionExpired(
                "Session has expired".to_string(),
            )));
        }

        if session.refresh_token != request.refresh_token {
            warn!("Invalid refresh token for session: {}", session_id);
            return Err(ControlCenterError::Auth(auth::AuthError::Authentication(
                "Invalid refresh token".to_string(),
            )));
        }

        // Get user to refresh roles
        let user = self.user_service.get_by_id(user_id).await?;

        // Update session last used
        let session_id = session.session_id;
        session.update_last_used();
        self.update_session(session).await?;

        // Generate new token pair
        let token_response =
            self.jwt_service
                .generate_token_pair(user.user_id, session_id, user.roles.clone())?;

        info!("Token refreshed for user: {}", user.username);

        Ok(token_response)
    }

    /// Logout user by invalidating session
    pub async fn logout(&self, request: LogoutRequest, user_id: Uuid) -> Result<()> {
        if let Some(session_id_str) = request.session_id {
            let session_id = Uuid::parse_str(&session_id_str)
                .map_err(|_| ControlCenterError::Http(http::bad_request("Invalid session ID")))?;

            self.invalidate_session(session_id).await?;
            info!("User {} logged out from session: {}", user_id, session_id);
        } else if request.all_sessions.unwrap_or(false) {
            self.invalidate_all_user_sessions(user_id).await?;
            info!("User {} logged out from all sessions", user_id);
        } else {
            return Err(ControlCenterError::Http(http::bad_request(
                "Must specify session_id or all_sessions",
            )));
        }

        Ok(())
    }

    /// Verify JWT token and return user context
    pub async fn verify_token(&self, token: &str) -> Result<(Uuid, Uuid, Vec<String>)> {
        let token_data = self.jwt_service.verify_access_token(token)?;

        let user_id = Uuid::parse_str(&token_data.claims.sub).map_err(|_| {
            ControlCenterError::Auth(auth::AuthError::Token(
                "Invalid user ID in token".to_string(),
            ))
        })?;

        let session_id = Uuid::parse_str(&token_data.claims.session_id).map_err(|_| {
            ControlCenterError::Auth(auth::AuthError::Token(
                "Invalid session ID in token".to_string(),
            ))
        })?;

        // Verify session is still active
        let session = self.get_session_by_id(session_id).await?;
        if !session.is_active || session.is_expired() {
            return Err(ControlCenterError::Auth(auth::AuthError::SessionExpired(
                "Session is no longer valid".to_string(),
            )));
        }

        Ok((user_id, session_id, token_data.claims.roles))
    }

    /// Create a new session for user
    async fn create_session(
        &self,
        user_id: Uuid,
        client_info: Option<ClientInfo>,
    ) -> Result<Session> {
        let refresh_token = self.generate_refresh_token_string(user_id)?;
        let session = Session::new(user_id, refresh_token, 24 * 7, client_info); // 7 days

        let created_session: Session = self
            .db
            .db
            .create("sessions")
            .content(session)
            .await?
            .ok_or_else(|| {
                ControlCenterError::from(crate::error::database::DatabaseError::Database(
                    "Failed to create session".to_string(),
                ))
            })?;

        Ok(created_session)
    }

    /// Get session by ID
    async fn get_session_by_id(&self, session_id: Uuid) -> Result<Session> {
        let query = "SELECT * FROM sessions WHERE session_id = $session_id";
        let mut result = self
            .db
            .db
            .query(query)
            .bind(("session_id", session_id))
            .await?;

        let sessions: Vec<Session> = result.take(0)?;
        sessions
            .into_iter()
            .next()
            .ok_or_else(|| ControlCenterError::SessionNotFound(session_id.to_string()))
    }

    /// Update session
    async fn update_session(&self, session: Session) -> Result<()> {
        let query = "UPDATE sessions SET last_used = $last_used WHERE session_id = $session_id";
        self.db
            .db
            .query(query)
            .bind(("last_used", session.last_used))
            .bind(("session_id", session.session_id))
            .await?;

        Ok(())
    }

    /// Invalidate session by ID
    async fn invalidate_session(&self, session_id: Uuid) -> Result<()> {
        let query = "UPDATE sessions SET is_active = false WHERE session_id = $session_id";
        self.db
            .db
            .query(query)
            .bind(("session_id", session_id))
            .await?;

        Ok(())
    }

    /// Invalidate all sessions for a user
    async fn invalidate_all_user_sessions(&self, user_id: Uuid) -> Result<()> {
        let query = "UPDATE sessions SET is_active = false WHERE user_id = $user_id";
        self.db.db.query(query).bind(("user_id", user_id)).await?;

        Ok(())
    }

    /// Verify password against hash
    pub fn verify_password(&self, password: &str, password_hash: &str) -> Result<bool> {
        let parsed_hash = PasswordHash::new(password_hash).map_err(|e| {
            ControlCenterError::Auth(auth::AuthError::Authentication(format!(
                "Invalid hash: {}",
                e
            )))
        })?;

        let argon2 = Argon2::default();

        match argon2.verify_password(password.as_bytes(), &parsed_hash) {
            Ok(()) => Ok(true),
            Err(argon2::password_hash::Error::Password) => Ok(false),
            Err(e) => Err(ControlCenterError::Auth(auth::AuthError::Authentication(
                format!("Password verification failed: {}", e),
            ))),
        }
    }

    /// Hash password using Argon2
    pub fn hash_password(password: &str) -> Result<String> {
        let salt = SaltString::generate(&mut OsRng);
        let argon2 = Argon2::default();

        argon2
            .hash_password(password.as_bytes(), &salt)
            .map(|hash| hash.to_string())
            .map_err(|e| {
                ControlCenterError::Auth(auth::AuthError::Authentication(format!(
                    "Password hashing failed: {}",
                    e
                )))
            })
    }

    /// Generate a refresh token string (different from JWT)
    fn generate_refresh_token_string(&self, user_id: Uuid) -> Result<String> {
        // For simplicity, we'll use the JWT service to generate the refresh token
        // In production, you might want a different approach
        self.jwt_service
            .generate_refresh_token(user_id, Uuid::new_v4())
    }

    /// Get active sessions for a user
    pub async fn get_user_sessions(&self, user_id: Uuid) -> Result<Vec<Session>> {
        let query = "SELECT * FROM sessions WHERE user_id = $user_id AND is_active = true AND \
                     expires_at > time::now()";
        let mut result = self.db.db.query(query).bind(("user_id", user_id)).await?;

        let sessions: Vec<Session> = result.take(0)?;
        Ok(sessions)
    }

    /// Clean up expired sessions
    pub async fn cleanup_expired_sessions(&self) -> Result<usize> {
        self.db.cleanup_expired_sessions().await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_password_hashing() {
        let password = "test_password_123";
        let hash = AuthService::hash_password(password).unwrap();

        // Hash should be different from password
        assert_ne!(hash, password);

        // Hash should be valid Argon2 format
        assert!(hash.starts_with("$argon2"));

        // Should be able to create AuthService and verify
        // Note: This test would require actual services in a full test
    }

    #[test]
    fn test_password_verification() {
        let password = "test_password_123";
        let hash = AuthService::hash_password(password).unwrap();

        // Create a minimal auth service (this would need proper setup in real tests)
        // For now, just test the hash format
        let parsed = PasswordHash::new(&hash);
        assert!(parsed.is_ok());
    }
}
