//! Nushell plugin for provisioning authentication (JWT, MFA).
//!
//! This plugin provides authentication commands for the provisioning platform:
//! - `auth login` - Authenticate with JWT
//! - `auth logout` - Revoke authentication session
//! - `auth verify` - Verify token validity
//! - `auth sessions` - List active sessions
//! - `auth mfa enroll` - Enroll in MFA
//! - `auth mfa verify` - Verify MFA code

use nu_plugin::{
    serve_plugin, EngineInterface, EvaluatedCall, MsgPackSerializer, Plugin, PluginCommand,
    SimplePluginCommand,
};
use nu_protocol::{record, Category, Example, LabeledError, Signature, SyntaxShape, Type, Value};

pub mod auth;
pub mod error;
mod helpers;
pub mod keyring;

#[cfg(test)]
mod tests;

use crate::auth::DEFAULT_CONTROL_CENTER_URL;

/// Nushell plugin for provisioning authentication (JWT, MFA).
#[derive(Debug)]
pub struct AuthPlugin;

impl Plugin for AuthPlugin {
    /// Returns the plugin version from Cargo.toml.
    fn version(&self) -> String {
        env!("CARGO_PKG_VERSION").into()
    }

    /// Returns the list of commands provided by this plugin.
    fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
        vec![
            Box::new(Login),
            Box::new(Logout),
            Box::new(Verify),
            Box::new(Sessions),
            Box::new(MfaEnroll),
            Box::new(MfaVerify),
        ]
    }
}

// =============================================================================
// Login Command
// =============================================================================

/// Login command - Authenticate with the provisioning platform.
#[derive(Debug)]
pub struct Login;

impl SimplePluginCommand for Login {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth login"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .required("username", SyntaxShape::String, "Username for login")
            .optional(
                "password",
                SyntaxShape::String,
                "Password (will prompt if omitted)",
            )
            .named(
                "url",
                SyntaxShape::String,
                "Control center URL (default: http://localhost:8081)",
                None,
            )
            .switch("save", "Save credentials to secure keyring", None)
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Login to provisioning platform with JWT authentication"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth login admin",
                description: "Login as admin (password prompt)",
                result: None,
            },
            Example {
                example: "auth login admin mypassword",
                description: "Login with password in command",
                result: None,
            },
            Example {
                example: "auth login admin --url http://control.example.com:8081",
                description: "Login to custom control center URL",
                result: None,
            },
            Example {
                example: "auth login admin --save",
                description: "Login and save credentials to keyring",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let username: String = call.req(0)?;
        let password_arg: Option<String> = call.opt(1)?;
        let url = call
            .get_flag::<String>("url")?
            .unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());
        let save_token = call.has_flag("save")?;

        // Get password (from arg or prompt)
        let password = if let Some(pwd) = password_arg {
            pwd
        } else {
            helpers::prompt_password("Password: ")
                .map_err(|e| LabeledError::new(e.to_string()))?
        };

        // Send login request
        let token_response = helpers::send_login_request(&url, &username, &password)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Store tokens in keyring if requested
        if save_token {
            keyring::store_tokens(
                &username,
                &token_response.access_token,
                &token_response.refresh_token,
            )
            .map_err(|e| LabeledError::new(e.to_string()))?;
        }

        // Return success response
        Ok(Value::record(
            record! {
                "success" => Value::bool(true, call.head),
                "user" => Value::record(record! {
                    "id" => Value::string(&token_response.user.id, call.head),
                    "username" => Value::string(&token_response.user.username, call.head),
                    "email" => Value::string(&token_response.user.email, call.head),
                    "roles" => Value::list(
                        token_response.user.roles.iter()
                            .map(|r| Value::string(r, call.head))
                            .collect(),
                        call.head
                    ),
                }, call.head),
                "expires_in" => Value::int(token_response.expires_in, call.head),
                "token_saved" => Value::bool(save_token, call.head),
            },
            call.head,
        ))
    }
}

// =============================================================================
// Logout Command
// =============================================================================

/// Logout command - Remove authentication session.
#[derive(Debug)]
pub struct Logout;

impl SimplePluginCommand for Logout {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth logout"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .named(
                "user",
                SyntaxShape::String,
                "Username (defaults to current user)",
                Some('u'),
            )
            .named("url", SyntaxShape::String, "Control Center URL", None)
            .switch("all", "Logout from all active sessions", Some('a'))
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Logout from provisioning platform"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth logout",
                description: "Logout from current session",
                result: None,
            },
            Example {
                example: "auth logout --all",
                description: "Logout from all active sessions",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let username_arg: Option<String> = call.get_flag("user")?;
        let _all_sessions = call.has_flag("all")?;
        let url = call
            .get_flag::<String>("url")?
            .unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());

        // Get username (from flag or current user)
        let username = username_arg.unwrap_or_else(keyring::get_current_username);

        // Get access token
        let access_token = keyring::get_access_token(&username)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Send logout request
        helpers::send_logout_request(&url, &access_token)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Remove tokens from keyring
        keyring::remove_tokens(&username)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        Ok(Value::record(
            record! {
                "success" => Value::bool(true, call.head),
                "message" => Value::string("Logged out successfully", call.head),
                "user" => Value::string(&username, call.head),
            },
            call.head,
        ))
    }
}

// =============================================================================
// Verify Command
// =============================================================================

/// Verify token command - Check authentication status.
#[derive(Debug)]
pub struct Verify;

impl SimplePluginCommand for Verify {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth verify"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .named(
                "token",
                SyntaxShape::String,
                "Token to verify (uses stored token if omitted)",
                None,
            )
            .named(
                "user",
                SyntaxShape::String,
                "Username for stored token lookup",
                Some('u'),
            )
            .named(
                "url",
                SyntaxShape::String,
                "Control Center URL for remote verification",
                None,
            )
            .switch("local", "Verify locally without contacting server", Some('l'))
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Verify current authentication token"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth verify",
                description: "Verify stored authentication token",
                result: None,
            },
            Example {
                example: "auth verify --token eyJhbGc...",
                description: "Verify specific token",
                result: None,
            },
            Example {
                example: "auth verify --local",
                description: "Verify token locally (no server contact)",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let token_arg: Option<String> = call.get_flag("token")?;
        let username_arg: Option<String> = call.get_flag("user")?;
        let url_arg: Option<String> = call.get_flag("url")?;
        let local_only = call.has_flag("local")?;

        // Get token (from arg or keyring)
        let token = if let Some(t) = token_arg {
            t
        } else {
            let username = username_arg.unwrap_or_else(keyring::get_current_username);
            keyring::get_access_token(&username)
                .map_err(|e| LabeledError::new(e.to_string()))?
        };

        if local_only {
            // Local verification (no network)
            let result = auth::verify_token_local(&token)
                .map_err(|e| LabeledError::new(e.to_string()))?;

            Ok(Value::record(
                record! {
                    "valid" => Value::bool(result.valid, call.head),
                    "verification_type" => Value::string("local", call.head),
                    "expires_in" => result.expires_in
                        .map(|s| Value::int(s, call.head))
                        .unwrap_or(Value::nothing(call.head)),
                    "user_id" => result.claims.as_ref()
                        .map(|c| Value::string(&c.sub, call.head))
                        .unwrap_or(Value::nothing(call.head)),
                    "username" => result.claims.as_ref()
                        .map(|c| Value::string(&c.username, call.head))
                        .unwrap_or(Value::nothing(call.head)),
                    "roles" => result.claims.as_ref()
                        .map(|c| Value::list(
                            c.roles.iter().map(|r| Value::string(r, call.head)).collect(),
                            call.head
                        ))
                        .unwrap_or(Value::nothing(call.head)),
                    "error" => result.error
                        .map(|e| Value::string(&e, call.head))
                        .unwrap_or(Value::nothing(call.head)),
                },
                call.head,
            ))
        } else {
            // Remote verification
            let url = url_arg.unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());
            let result = helpers::verify_token(&url, &token)
                .map_err(|e| LabeledError::new(e.to_string()))?;

            Ok(helpers::verify_response_to_value(&result, call.head))
        }
    }
}

// =============================================================================
// Sessions Command
// =============================================================================

/// Sessions command - List active authentication sessions.
#[derive(Debug)]
pub struct Sessions;

impl SimplePluginCommand for Sessions {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth sessions"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(
                Type::Nothing,
                Type::List(Box::new(Type::Record(vec![].into()))),
            )
            .switch("active", "Show only active sessions", None)
            .named(
                "user",
                SyntaxShape::String,
                "Username for token lookup",
                Some('u'),
            )
            .named("url", SyntaxShape::String, "Control Center URL", None)
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "List active authentication sessions"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth sessions",
                description: "List all sessions",
                result: None,
            },
            Example {
                example: "auth sessions --active",
                description: "List only active sessions",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let active_only = call.has_flag("active")?;
        let username_arg: Option<String> = call.get_flag("user")?;
        let url = call
            .get_flag::<String>("url")?
            .unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());

        // Get username and access token
        let username = username_arg.unwrap_or_else(keyring::get_current_username);
        let access_token = keyring::get_access_token(&username)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // List sessions from server
        let sessions = helpers::list_sessions(&url, &access_token, active_only)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        let session_values: Vec<Value> = sessions
            .iter()
            .map(|s| helpers::session_info_to_value(s, call.head))
            .collect();

        Ok(Value::list(session_values, call.head))
    }
}

// =============================================================================
// MFA Enroll Command
// =============================================================================

/// MFA Enrollment command.
#[derive(Debug)]
pub struct MfaEnroll;

impl SimplePluginCommand for MfaEnroll {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth mfa enroll"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .required("type", SyntaxShape::String, "MFA type: totp or webauthn")
            .named("user", SyntaxShape::String, "Username", Some('u'))
            .named("url", SyntaxShape::String, "Control Center URL", None)
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Enroll in MFA (TOTP or WebAuthn)"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth mfa enroll totp",
                description: "Enroll TOTP (Google Authenticator, Authy)",
                result: None,
            },
            Example {
                example: "auth mfa enroll webauthn",
                description: "Enroll WebAuthn (YubiKey, Touch ID)",
                result: None,
            },
            Example {
                example: "auth mfa enroll totp --user alice",
                description: "Enroll TOTP for specific user",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let mfa_type: String = call.req(0)?;
        let username = call
            .get_flag::<String>("user")?
            .unwrap_or_else(keyring::get_current_username);
        let url = call
            .get_flag::<String>("url")?
            .unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());

        // Validate MFA type
        if mfa_type != "totp" && mfa_type != "webauthn" {
            return Err(LabeledError::new(format!(
                "Invalid MFA type '{}'. Use 'totp' or 'webauthn'",
                mfa_type
            )));
        }

        // Get access token
        let access_token = keyring::get_access_token(&username)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Send enrollment request
        let response = helpers::send_mfa_enroll_request(&url, &access_token, &mfa_type)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Display QR code if TOTP
        if mfa_type == "totp" {
            helpers::display_qr_code(&response.qr_code_uri)
                .map_err(|e| LabeledError::new(e.to_string()))?;
        }

        Ok(Value::record(
            record! {
                "success" => Value::bool(true, call.head),
                "mfa_type" => Value::string(&mfa_type, call.head),
                "secret" => Value::string(&response.secret, call.head),
                "backup_codes" => Value::list(
                    response.backup_codes.iter()
                        .map(|c| Value::string(c, call.head))
                        .collect(),
                    call.head
                ),
            },
            call.head,
        ))
    }
}

// =============================================================================
// MFA Verify Command
// =============================================================================

/// MFA Verify command.
#[derive(Debug)]
pub struct MfaVerify;

impl SimplePluginCommand for MfaVerify {
    type Plugin = AuthPlugin;

    fn name(&self) -> &str {
        "auth mfa verify"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .named("code", SyntaxShape::String, "6-digit TOTP code", Some('c'))
            .named("user", SyntaxShape::String, "Username", Some('u'))
            .named("url", SyntaxShape::String, "Control Center URL", None)
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Verify MFA code"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "auth mfa verify --code 123456",
                description: "Verify TOTP code",
                result: None,
            },
            Example {
                example: "auth mfa verify --code 123456 --user alice",
                description: "Verify TOTP code for specific user",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &AuthPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let code = call
            .get_flag::<String>("code")?
            .ok_or_else(|| LabeledError::new("--code is required"))?;

        // Validate code format
        if code.len() != 6 || !code.chars().all(|c| c.is_ascii_digit()) {
            return Err(LabeledError::new(
                "Code must be a 6-digit number",
            ));
        }

        let username = call
            .get_flag::<String>("user")?
            .unwrap_or_else(keyring::get_current_username);
        let url = call
            .get_flag::<String>("url")?
            .unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());

        // Get access token
        let access_token = keyring::get_access_token(&username)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        // Verify code
        let valid = helpers::send_mfa_verify_request(&url, &access_token, &code)
            .map_err(|e| LabeledError::new(e.to_string()))?;

        Ok(Value::record(
            record! {
                "valid" => Value::bool(valid, call.head),
                "message" => Value::string(
                    if valid { "MFA verified successfully" } else { "Invalid code" },
                    call.head
                ),
            },
            call.head,
        ))
    }
}

/// Entry point for the plugin binary.
fn main() {
    serve_plugin(&AuthPlugin, MsgPackSerializer);
}
