use anyhow::Result;
use chrono::{Duration, Utc};
use shared::auth::{
    AuthError, AuthResponse, BackupCodesResponse, ChangePasswordRequest, Disable2FARequest,
    GenerateBackupCodesRequest, Login2FARequest, LoginCredentials, OAuthProvider,
    PasswordResetConfirm, PasswordResetRequest, RefreshTokenRequest, RegisterUserData,
    Setup2FARequest, Setup2FAResponse, TwoFactorStatus, UpdateUserData, User, Verify2FARequest,
};
use std::sync::Arc;
use time::OffsetDateTime;
use tower_cookies::{Cookie, Cookies};
use uuid::Uuid;

use super::{
    jwt::JwtService,
    oauth::{OAuthAuthorizationUrl, OAuthCallback, OAuthService},
    password::PasswordService,
    repository::{AuthRepository, AuthRepositoryTrait, CreateSessionRequest, CreateUserRequest},
    two_factor::TwoFactorService,
};

#[derive(Clone)]
pub struct AuthService {
    pub jwt_service: Arc<JwtService>,
    pub oauth_service: Arc<OAuthService>,
    pub password_service: Arc<PasswordService>,
    pub repository: Arc<AuthRepository>,
    pub two_factor_service: Arc<TwoFactorService>,
}

impl AuthService {
    pub fn new(
        jwt_service: Arc<JwtService>,
        oauth_service: Arc<OAuthService>,
        password_service: Arc<PasswordService>,
        repository: Arc<AuthRepository>,
        two_factor_service: Arc<TwoFactorService>,
    ) -> Self {
        Self {
            jwt_service,
            oauth_service,
            password_service,
            repository,
            two_factor_service,
        }
    }

    /// Register a new user with email and password
    pub async fn register_user(&self, data: RegisterUserData) -> Result<AuthResponse, AuthError> {
        // Validate input
        if data.email.is_empty() || data.username.is_empty() || data.password.is_empty() {
            return Err(AuthError::ValidationError(
                "Email, username, and password are required".to_string(),
            ));
        }

        // Check if email already exists
        if self
            .repository
            .email_exists(&data.email)
            .await
            .map_err(|_| AuthError::DatabaseError)?
        {
            return Err(AuthError::EmailAlreadyExists);
        }

        // Check if username already exists
        if self
            .repository
            .username_exists(&data.username)
            .await
            .map_err(|_| AuthError::DatabaseError)?
        {
            return Err(AuthError::UsernameAlreadyExists);
        }

        // Validate password strength
        if let Err(errors) = self
            .password_service
            .validate_password_strength(&data.password)
        {
            return Err(AuthError::ValidationError(errors.join(", ")));
        }

        // Hash password
        let password_hash = self
            .password_service
            .hash_password(&data.password)
            .map_err(|_| AuthError::InternalError)?;

        // Create user
        let create_request = CreateUserRequest {
            email: data.email.clone(),
            username: Some(data.username.clone()),
            password_hash,
            display_name: data.display_name.clone(),
            is_verified: false,
            is_active: true,
        };
        let user = self
            .repository
            .create_user(&create_request)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Generate tokens
        let token_pair = self
            .jwt_service
            .generate_token_pair(&user.clone().into())
            .map_err(|_| AuthError::InternalError)?;

        Ok(AuthResponse {
            user: user.into(),
            access_token: token_pair.access_token,
            refresh_token: Some(token_pair.refresh_token),
            expires_in: token_pair.expires_in,
            token_type: "Bearer".to_string(),
            requires_2fa: false,
        })
    }

    /// Login with email and password (first step)
    pub async fn login(
        &self,
        credentials: LoginCredentials,
        cookies: Option<Cookies>,
    ) -> Result<AuthResponse, AuthError> {
        // Find user by email
        let user = self
            .repository
            .find_user_by_email(&credentials.email)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::InvalidCredentials)?;

        // Check if user is active
        if !user.is_active {
            return Err(AuthError::AccountSuspended);
        }

        // Verify password using the hash from the user record
        if !self
            .password_service
            .verify_password(&credentials.password, &user.password_hash)
            .map_err(|_| AuthError::InternalError)?
        {
            return Err(AuthError::InvalidCredentials);
        }

        // Check if 2FA is enabled
        let requires_2fa = self.two_factor_service.is_2fa_enabled(user.id).await?;

        if requires_2fa {
            // Return partial response indicating 2FA is required
            return Ok(AuthResponse {
                user: user.into(),
                access_token: String::new(), // Empty token
                refresh_token: None,
                expires_in: 0,
                token_type: "Bearer".to_string(),
                requires_2fa: true,
            });
        }

        // Complete login without 2FA
        self.complete_login(user.into(), credentials.remember_me, cookies)
            .await
    }

    /// Complete login after 2FA verification
    pub async fn login_with_2fa(
        &self,
        request: Login2FARequest,
        cookies: Option<Cookies>,
        ip_address: Option<String>,
        user_agent: Option<String>,
    ) -> Result<AuthResponse, AuthError> {
        // Find user by email
        let user = self
            .repository
            .find_user_by_email(&request.email)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::InvalidCredentials)?;

        // Check if user is active
        if !user.is_active {
            return Err(AuthError::AccountSuspended);
        }

        // Verify 2FA code
        self.two_factor_service
            .verify_2fa_for_login(user.id, &request.code, ip_address, user_agent)
            .await?;

        // Complete login
        self.complete_login(user.into(), request.remember_me, cookies)
            .await
    }

    /// Helper method to complete login process
    async fn complete_login(
        &self,
        user: User,
        remember_me: bool,
        cookies: Option<Cookies>,
    ) -> Result<AuthResponse, AuthError> {
        // Update last login
        self.repository
            .update_last_login(user.id)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Generate tokens
        let token_pair = self
            .jwt_service
            .generate_token_pair(&user)
            .map_err(|_| AuthError::InternalError)?;

        // Create session if remember_me is true
        if remember_me {
            if let Some(cookies) = cookies {
                let session_id = Uuid::new_v4().to_string();
                let expires_at = Utc::now() + Duration::days(30);

                let session_request = CreateSessionRequest {
                    user_id: user.id,
                    token: session_id.clone(),
                    expires_at,
                    user_agent: None,
                    ip_address: None,
                };
                self.repository
                    .create_session(&session_request)
                    .await
                    .map_err(|_| AuthError::DatabaseError)?;

                let mut cookie = Cookie::new("session_id", session_id);
                cookie.set_expires(Some(
                    OffsetDateTime::from_unix_timestamp(expires_at.timestamp())
                        .map_err(|_| AuthError::InternalError)?,
                ));
                cookie.set_http_only(true);
                cookie.set_secure(true);
                cookie.set_same_site(tower_cookies::cookie::SameSite::Strict);
                cookies.add(cookie);
            }
        }

        Ok(AuthResponse {
            user,
            access_token: token_pair.access_token,
            refresh_token: Some(token_pair.refresh_token),
            expires_in: token_pair.expires_in,
            token_type: token_pair.token_type,
            requires_2fa: false,
        })
    }

    /// Logout user
    pub async fn logout(&self, user_id: Uuid, cookies: Option<Cookies>) -> Result<(), AuthError> {
        // Invalidate all user sessions
        self.repository
            .invalidate_all_user_sessions(user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Remove session cookie
        if let Some(cookies) = cookies {
            let mut cookie = Cookie::new("session_id", "");
            cookie.set_expires(Some(
                OffsetDateTime::from_unix_timestamp((Utc::now() - Duration::days(1)).timestamp())
                    .map_err(|_| AuthError::InternalError)?,
            ));
            cookies.add(cookie);
        }

        Ok(())
    }

    /// Refresh access token
    pub async fn refresh_token(
        &self,
        request: RefreshTokenRequest,
    ) -> Result<AuthResponse, AuthError> {
        // Verify refresh token
        let refresh_claims = self
            .jwt_service
            .verify_refresh_token(&request.refresh_token)
            .map_err(|_| AuthError::InvalidToken)?;

        // Find user
        let user_id = Uuid::parse_str(&refresh_claims.sub).map_err(|_| AuthError::InvalidToken)?;
        let user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        // Check if user is still active
        if !user.is_active {
            return Err(AuthError::AccountSuspended);
        }

        // Generate new access token
        let new_access_token = self
            .jwt_service
            .refresh_access_token(&request.refresh_token, &user.clone().into())
            .map_err(|_| AuthError::InvalidToken)?;

        Ok(AuthResponse {
            user: user.into(),
            access_token: new_access_token,
            refresh_token: Some(request.refresh_token), // Return the same refresh token
            expires_in: 3600,                           // 1 hour
            token_type: "Bearer".to_string(),
            requires_2fa: false,
        })
    }

    /// Get OAuth authorization URL
    pub async fn get_oauth_authorization_url(
        &self,
        provider: &OAuthProvider,
    ) -> Result<OAuthAuthorizationUrl, AuthError> {
        self.oauth_service
            .get_authorization_url(provider)
            .map_err(|e| AuthError::OAuthError(e.to_string()))
    }

    /// Handle OAuth callback
    pub async fn handle_oauth_callback(
        &self,
        provider: &OAuthProvider,
        callback: OAuthCallback,
        pkce_verifier: Option<String>,
    ) -> Result<AuthResponse, AuthError> {
        // Exchange code for user info
        let oauth_user_info = self
            .oauth_service
            .handle_callback(provider, callback, pkce_verifier)
            .await
            .map_err(|e| AuthError::OAuthError(e.to_string()))?;

        // Try to find existing user by OAuth account
        if let Some(user) = self
            .repository
            .find_user_by_oauth_account(provider.as_str(), &oauth_user_info.provider_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
        {
            // Update last login
            self.repository
                .update_last_login(user.id)
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            // Generate tokens
            let token_pair = self
                .jwt_service
                .generate_token_pair(&user.clone().into())
                .map_err(|_| AuthError::InternalError)?;

            return Ok(AuthResponse {
                user: user.into(),
                access_token: token_pair.access_token,
                refresh_token: Some(token_pair.refresh_token),
                expires_in: token_pair.expires_in,
                token_type: token_pair.token_type,
                requires_2fa: false,
            });
        }

        // Try to find existing user by email
        if let Some(user) = self
            .repository
            .find_user_by_email(&oauth_user_info.email)
            .await
            .map_err(|_| AuthError::DatabaseError)?
        {
            // Link OAuth account to existing user
            self.repository
                .create_oauth_account(user.id, provider.as_str(), &oauth_user_info.provider_id)
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            // Update last login
            self.repository
                .update_last_login(user.id)
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            // Generate tokens
            let token_pair = self
                .jwt_service
                .generate_token_pair(&user.clone().into())
                .map_err(|_| AuthError::InternalError)?;

            return Ok(AuthResponse {
                user: user.into(),
                access_token: token_pair.access_token,
                refresh_token: Some(token_pair.refresh_token),
                expires_in: token_pair.expires_in,
                token_type: token_pair.token_type,
                requires_2fa: false,
            });
        }

        // Create new user
        let username = oauth_user_info
            .username
            .or_else(|| oauth_user_info.display_name.clone())
            .unwrap_or_else(|| format!("user_{}", Uuid::new_v4()));

        let create_request = CreateUserRequest {
            email: oauth_user_info.email.clone(),
            username: Some(username.clone()),
            password_hash: "".to_string(), // Empty password for OAuth users
            display_name: oauth_user_info.display_name.clone(),
            is_verified: true, // OAuth users are pre-verified
            is_active: true,
        };
        let user = self
            .repository
            .create_user(&create_request)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Create OAuth account
        self.repository
            .create_oauth_account(user.id, provider.as_str(), &oauth_user_info.provider_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Generate tokens
        let token_pair = self
            .jwt_service
            .generate_token_pair(&user.clone().into())
            .map_err(|_| AuthError::InternalError)?;

        Ok(AuthResponse {
            user: user.into(),
            access_token: token_pair.access_token,
            refresh_token: Some(token_pair.refresh_token),
            expires_in: token_pair.expires_in,
            token_type: token_pair.token_type,
            requires_2fa: false,
        })
    }

    /// Request password reset
    pub async fn request_password_reset(
        &self,
        request: PasswordResetRequest,
    ) -> Result<(), AuthError> {
        // Find user by email
        let user = self
            .repository
            .find_user_by_email(&request.email)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        // Generate reset token
        let (token, expires_at) = self.password_service.generate_password_reset_token();
        let token_hash = self
            .password_service
            .hash_password(&token)
            .map_err(|_| AuthError::InternalError)?;

        // Store token in database
        self.repository
            .create_token(user.id, "password_reset", &token_hash, expires_at)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // TODO: Send email with reset link containing the token
        // For now, we'll just log it
        tracing::info!("Password reset token for {}: {}", user.email, token);

        Ok(())
    }

    /// Confirm password reset
    pub async fn confirm_password_reset(
        &self,
        request: PasswordResetConfirm,
    ) -> Result<(), AuthError> {
        // Hash the token to compare with stored hash
        let token_hash = self
            .password_service
            .hash_password(&request.token)
            .map_err(|_| AuthError::InternalError)?;

        // Find token
        let (user_id, expires_at) = self
            .repository
            .find_token(&token_hash, "password_reset")
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::InvalidToken)?;

        // Validate new password
        if let Err(errors) = self
            .password_service
            .validate_password_strength(&request.new_password)
        {
            return Err(AuthError::ValidationError(errors.join(", ")));
        }

        // Hash new password
        let new_password_hash = self
            .password_service
            .hash_password(&request.new_password)
            .map_err(|_| AuthError::InternalError)?;

        // Update user password
        self.repository
            .update_password(&user_id, &new_password_hash)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Mark token as used
        self.repository
            .use_token(&token_hash, "password_reset")
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Invalidate all user sessions
        // Invalidate all user sessions after password change
        self.repository
            .invalidate_all_user_sessions(user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        Ok(())
    }

    /// Change password
    pub async fn change_password(
        &self,
        user_id: Uuid,
        request: ChangePasswordRequest,
    ) -> Result<(), AuthError> {
        // Get user to verify current password
        let user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        // Verify current password
        if !self
            .password_service
            .verify_password(&request.current_password, &user.password_hash)
            .map_err(|_| AuthError::InternalError)?
        {
            return Err(AuthError::InvalidCredentials);
        }

        // Validate new password
        if let Err(errors) = self
            .password_service
            .validate_password_strength(&request.new_password)
        {
            return Err(AuthError::ValidationError(errors.join(", ")));
        }

        // Hash new password
        let new_password_hash = self
            .password_service
            .hash_password(&request.new_password)
            .map_err(|_| AuthError::InternalError)?;

        // Update password
        self.repository
            .update_password(&user_id, &new_password_hash)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Invalidate all user sessions except current one
        self.repository
            .invalidate_all_user_sessions(user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        Ok(())
    }

    /// Update user profile
    pub async fn update_user_profile(
        &self,
        user_id: Uuid,
        data: UpdateUserData,
    ) -> Result<User, AuthError> {
        // Get current user
        let mut user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        // Update user fields
        if let Some(display_name) = data.display_name {
            user.display_name = Some(display_name);
        }
        // Note: first_name, last_name, bio, timezone, locale, preferences
        // would need to be added to DatabaseUser struct to be updated

        // Update user profile
        self.repository
            .update_user_profile(&user)
            .await
            .map_err(|_| AuthError::DatabaseError)?;

        // Return updated user
        Ok(user.into())
    }

    /// Get user by ID
    pub async fn get_user(&self, user_id: Uuid) -> Result<User, AuthError> {
        let db_user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;
        Ok(db_user.into())
    }

    /// Verify email
    pub async fn verify_email(&self, user_id: Uuid) -> Result<(), AuthError> {
        self.repository
            .verify_email(user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)
    }

    /// Get configured OAuth providers
    pub fn get_oauth_providers(&self) -> Vec<OAuthProvider> {
        self.oauth_service.get_configured_providers()
    }

    /// Cleanup expired tokens and sessions
    pub async fn cleanup_expired(&self) -> Result<(), AuthError> {
        self.repository
            .cleanup_expired_tokens()
            .await
            .map_err(|_| AuthError::DatabaseError)?;
        Ok(())
    }

    // 2FA Methods

    /// Setup 2FA for a user
    pub async fn setup_2fa(
        &self,
        user_id: Uuid,
        request: Setup2FARequest,
    ) -> Result<Setup2FAResponse, AuthError> {
        // Get user to verify password and get email
        let user = self.get_user(user_id).await?;

        // Verify current password
        let db_user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        if !self
            .password_service
            .verify_password(&request.password, &db_user.password_hash)
            .map_err(|_| AuthError::InternalError)?
        {
            return Err(AuthError::InvalidCredentials);
        }

        // Setup 2FA
        self.two_factor_service
            .setup_2fa(user_id, &user.email, request)
            .await
    }

    /// Verify 2FA code and enable 2FA
    pub async fn verify_2fa_setup(
        &self,
        user_id: Uuid,
        request: Verify2FARequest,
        ip_address: Option<String>,
        user_agent: Option<String>,
    ) -> Result<(), AuthError> {
        self.two_factor_service
            .verify_and_enable_2fa(user_id, &request.code, ip_address, user_agent)
            .await
    }

    /// Get 2FA status for a user
    pub async fn get_2fa_status(&self, user_id: Uuid) -> Result<TwoFactorStatus, AuthError> {
        self.two_factor_service.get_2fa_status(user_id).await
    }

    /// Disable 2FA for a user
    pub async fn disable_2fa(
        &self,
        user_id: Uuid,
        request: Disable2FARequest,
        ip_address: Option<String>,
        user_agent: Option<String>,
    ) -> Result<(), AuthError> {
        // Verify current password
        let db_user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        if !self
            .password_service
            .verify_password(&request.password, &db_user.password_hash)
            .map_err(|_| AuthError::InternalError)?
        {
            return Err(AuthError::InvalidCredentials);
        }

        self.two_factor_service
            .disable_2fa(user_id, request, ip_address, user_agent)
            .await
    }

    /// Generate new backup codes
    pub async fn generate_backup_codes(
        &self,
        user_id: Uuid,
        request: GenerateBackupCodesRequest,
        ip_address: Option<String>,
        user_agent: Option<String>,
    ) -> Result<BackupCodesResponse, AuthError> {
        // Verify current password
        let db_user = self
            .repository
            .find_user_by_id(&user_id)
            .await
            .map_err(|_| AuthError::DatabaseError)?
            .ok_or(AuthError::UserNotFound)?;

        if !self
            .password_service
            .verify_password(&request.password, &db_user.password_hash)
            .map_err(|_| AuthError::InternalError)?
        {
            return Err(AuthError::InvalidCredentials);
        }

        self.two_factor_service
            .generate_new_backup_codes(user_id, request, ip_address, user_agent)
            .await
    }
}
