//! Login flow and authentication client
//!
//! Handles user authentication including username/password prompts,
//! MFA (TOTP) support, and token refresh.

use anyhow::{Context, Result};
use rpassword::read_password;
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use tonic::transport::Channel;

use super::token_manager::{AuthTokenManager, TokenInfo, TokenStorage};

/// Login request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
    /// Username
    pub username: String,
    /// Password
    pub password: String,
}

/// Login response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginResponse {
    /// Access token (JWT)
    pub access_token: String,
    /// Refresh token
    pub refresh_token: String,
    /// Token expiration time (Unix timestamp in seconds)
    pub expires_at: u64,
    /// Whether MFA is required
    pub mfa_required: bool,
    /// Temporary session ID for MFA verification
    pub session_id: Option<String>,
}

/// MFA verification request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaRequest {
    /// Temporary session ID from initial login
    pub session_id: String,
    /// TOTP code from authenticator app
    pub totp_code: String,
}

/// Token refresh request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshRequest {
    /// Refresh token
    pub refresh_token: String,
}

/// Token refresh response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshResponse {
    /// New access token (JWT)
    pub access_token: String,
    /// New refresh token (for token rotation)
    pub refresh_token: Option<String>,
    /// Token expiration time (Unix timestamp in seconds)
    pub expires_at: u64,
}

/// Login client for API authentication
pub struct LoginClient {
    /// gRPC channel to the API (used for future gRPC auth calls)
    gateway_channel: Channel,
}

impl LoginClient {
    /// Create a new login client
    pub fn new(gateway_channel: Channel) -> Self {
        Self { gateway_channel }
    }

    /// Get a reference to the underlying gateway channel
    pub fn gateway_channel(&self) -> &Channel {
        &self.gateway_channel
    }

    /// Check whether an API URL has been configured in the environment
    fn is_api_available() -> bool {
        std::env::var("API_URL").is_ok()
    }

    /// Perform interactive login flow
    ///
    /// Prompts user for credentials and handles MFA if required.
    pub async fn interactive_login<S: TokenStorage>(
        &self,
        auth_manager: &AuthTokenManager<S>,
    ) -> Result<()> {
        println!("\n=== Foxhunt FXT Authentication ===\n");

        // Prompt for username
        print!("Username: ");
        io::stdout().flush()?;
        let mut raw_username = String::new();
        io::stdin().read_line(&mut raw_username)?;
        let username = raw_username.trim().to_owned();

        // Prompt for password (hidden input)
        print!("Password: ");
        io::stdout().flush()?;
        let password = read_password().context("Failed to read password")?;

        // Attempt login
        let _login_request = LoginRequest { username, password };

        // TODO: Call API login endpoint via gRPC
        // For now, simulate a successful login response
        if Self::is_api_available() {
            tracing::debug!(
                "API configured but gRPC client not yet implemented -- using simulation"
            );
        }
        tracing::warn!(
            "SECURITY: Using simulated login response -- not suitable for production"
        );

        let response = self.simulate_login_response();

        if response.mfa_required {
            let session_id = response.session_id.as_deref().unwrap_or("");
            self.handle_mfa_flow(auth_manager, session_id).await?;
        } else {
            let token_info = TokenInfo {
                access_token: response.access_token,
                refresh_token: response.refresh_token,
                expires_at: response.expires_at,
            };

            auth_manager.set_tokens(token_info).await?;
            println!("\n\u{2705} Authentication successful!\n");
        }

        Ok(())
    }

    /// Perform non-interactive login with provided credentials
    ///
    /// Uses the same authentication flow as `interactive_login` but without
    /// prompting for user input. MFA is not supported in non-interactive mode.
    pub async fn login_with_credentials<S: TokenStorage>(
        &self,
        username: &str,
        password: &str,
        auth_manager: &AuthTokenManager<S>,
    ) -> Result<()> {
        use crate::proto::auth::auth_service_client::AuthServiceClient;

        let mut client = AuthServiceClient::new(self.gateway_channel.clone());
        let request = crate::proto::auth::LoginRequest {
            username: username.to_owned(),
            password: password.to_owned(),
        };

        let resp = client
            .login(request)
            .await
            .map_err(|e| anyhow::anyhow!("Login failed: {e}"))?
            .into_inner();

        let token_info = TokenInfo {
            access_token: resp.access_token,
            refresh_token: resp.refresh_token,
            #[allow(clippy::cast_sign_loss)]
            expires_at: resp.expires_at as u64,
        };

        auth_manager.set_tokens(token_info).await?;

        Ok(())
    }

    /// Handle MFA verification flow
    async fn handle_mfa_flow<S: TokenStorage>(
        &self,
        auth_manager: &AuthTokenManager<S>,
        session_id: &str,
    ) -> Result<()> {
        println!("\n\u{1f510} Multi-Factor Authentication Required\n");

        print!("Enter TOTP code from your authenticator app: ");
        io::stdout().flush()?;

        let mut raw_totp = String::new();
        io::stdin().read_line(&mut raw_totp)?;
        let totp_code = raw_totp.trim().to_owned();

        // Validate TOTP format (6 digits)
        if !totp_code.chars().all(|c| c.is_numeric()) || totp_code.len() != 6 {
            anyhow::bail!("Invalid TOTP code format - must be 6 digits");
        }

        let _mfa_request = MfaRequest {
            session_id: session_id.to_owned(),
            totp_code,
        };

        // TODO: Call API MFA endpoint via gRPC
        if Self::is_api_available() {
            tracing::debug!(
                "API configured but gRPC client not yet implemented -- using simulation"
            );
        }
        tracing::warn!(
            "SECURITY: Using simulated MFA response -- not suitable for production"
        );

        let response = self.simulate_mfa_response();

        let token_info = TokenInfo {
            access_token: response.access_token,
            refresh_token: response.refresh_token,
            expires_at: response.expires_at,
        };

        auth_manager.set_tokens(token_info).await?;
        println!("\n\u{2705} MFA verification successful!\n");

        Ok(())
    }

    /// Attempt to refresh tokens using stored refresh token
    pub async fn refresh_tokens<S: TokenStorage>(
        &self,
        auth_manager: &AuthTokenManager<S>,
    ) -> Result<()> {
        tracing::info!("Refreshing authentication tokens");

        // Get refresh token from storage
        let refresh_token = auth_manager
            .get_refresh_token()
            .await?
            .context("No refresh token available")?;

        let _refresh_request = RefreshRequest {
            refresh_token: refresh_token.clone(),
        };

        // TODO: Call API refresh endpoint via gRPC
        if Self::is_api_available() {
            tracing::debug!(
                "API configured but gRPC client not yet implemented -- using simulation"
            );
        }
        tracing::warn!(
            "SECURITY: Using simulated refresh response -- not suitable for production"
        );

        let response = self.simulate_refresh_response(&refresh_token);

        // Calculate new expiration (response.expires_at already contains the timestamp)
        let expires_at = response.expires_at;

        // Store new tokens in keyring (use set_tokens for proper storage)
        let token_info = TokenInfo {
            access_token: response.access_token.clone(),
            refresh_token: response.refresh_token.unwrap_or(refresh_token), // Use new token if provided, else keep old one
            expires_at,
        };

        auth_manager
            .set_tokens(token_info)
            .await
            .context("Failed to store refreshed tokens")?;

        tracing::info!("\u{2713} Tokens refreshed and stored in keyring");

        Ok(())
    }

    /// Attempt silent authentication using stored refresh token
    pub async fn silent_login<S: TokenStorage>(
        &self,
        auth_manager: &AuthTokenManager<S>,
    ) -> Result<bool> {
        if let Ok(Some(_)) = auth_manager.get_refresh_token().await {
            tracing::info!("Found stored refresh token - attempting silent login");

            match self.refresh_tokens(auth_manager).await {
                Ok(()) => {
                    tracing::info!("Silent login successful");
                    Ok(true)
                },
                Err(e) => {
                    tracing::debug!(
                        "Silent login could not refresh token: {} - will require interactive login",
                        e
                    );
                    Ok(false)
                },
            }
        } else {
            tracing::info!("No stored refresh token - interactive login required");
            Ok(false)
        }
    }

    // ===== SIMULATION METHODS (temporary until API Gateway gRPC is implemented) =====

    fn simulate_login_response(&self) -> LoginResponse {
        use super::jwt_generator::{generate_access_token, generate_refresh_token};
        use std::time::{SystemTime, UNIX_EPOCH};

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        // Generate proper JWT tokens
        let (access_token, _access_jti) = generate_access_token(
            "default",
            vec!["trader".to_owned()],
            vec!["api.access".to_owned(), "trading.execute".to_owned()],
            900, // 15 minutes
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        let (refresh_token, _refresh_jti) = generate_refresh_token(
            "default", 7200, // 2 hours
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        LoginResponse {
            access_token,
            refresh_token,
            expires_at: now + 900_u64, // 15 minutes
            mfa_required: false,
            session_id: None,
        }
    }

    fn simulate_mfa_response(&self) -> LoginResponse {
        use super::jwt_generator::{generate_access_token, generate_refresh_token};
        use std::time::{SystemTime, UNIX_EPOCH};

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        // Generate proper JWT tokens after MFA
        let (access_token, _access_jti) = generate_access_token(
            "default",
            vec!["trader".to_owned()],
            vec!["api.access".to_owned(), "trading.execute".to_owned()],
            900, // 15 minutes
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        let (refresh_token, _refresh_jti) = generate_refresh_token(
            "default", 7200, // 2 hours
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        LoginResponse {
            access_token,
            refresh_token,
            expires_at: now + 900_u64,
            mfa_required: false,
            session_id: None,
        }
    }

    fn simulate_refresh_response(&self, _refresh_token: &str) -> RefreshResponse {
        use super::jwt_generator::{generate_access_token, generate_refresh_token};
        use std::time::{SystemTime, UNIX_EPOCH};

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        // Generate new JWT tokens for refresh
        let (access_token, _access_jti) = generate_access_token(
            "default",
            vec!["trader".to_owned()],
            vec!["api.access".to_owned(), "trading.execute".to_owned()],
            900, // 15 minutes
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        let (new_refresh_token, _refresh_jti) = generate_refresh_token(
            "default", 7200, // 2 hours
        )
        .unwrap_or_else(|_| (String::new(), String::new()));

        RefreshResponse {
            access_token,
            refresh_token: Some(new_refresh_token), // Enable token rotation
            expires_at: now + 900_u64,
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use crate::auth::token_manager::InMemoryTokenStorage;

    #[tokio::test]
    async fn test_silent_login_without_refresh_token() {
        let channel = Channel::from_static("https://localhost:50050").connect_lazy();
        let client = LoginClient::new(channel);

        let storage = InMemoryTokenStorage::new();
        let manager = AuthTokenManager::new(storage);

        let result = client.silent_login(&manager).await.unwrap();
        assert!(!result); // Should return false (no stored token)
    }
}
