mod operator_cli;
mod operator_tooling;
mod s3_listener;

use operator_cli::{parse_operator_command, OperatorCommand, S3ServeStorage, HELP};

#[derive(Debug)]
pub enum CliError {
    Usage(String),
    Runtime(anyhow::Error),
}

impl std::fmt::Display for CliError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Usage(message) => write!(f, "{message}"),
            Self::Runtime(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for CliError {}

impl From<anyhow::Error> for CliError {
    fn from(error: anyhow::Error) -> Self {
        Self::Runtime(error)
    }
}

pub fn run_env() -> Result<(), CliError> {
    run(std::env::args().collect())
}

pub fn run(args: Vec<String>) -> Result<(), CliError> {
    let mut command = parse_operator_command(&args).map_err(CliError::Usage)?;
    let mut quiet = false;
    let mut verbosity = 0;
    if let OperatorCommand::Global {
        options,
        command: inner,
    } = command
    {
        quiet = options.quiet;
        verbosity = options.verbose;
        command = *inner;
    }

    if quiet
        && matches!(
            command,
            OperatorCommand::Help | OperatorCommand::DefaultStatus | OperatorCommand::Version
        )
    {
        return Ok(());
    }

    match command {
        OperatorCommand::Help => {
            print!("{HELP}");
            return Ok(());
        }
        OperatorCommand::Version => {
            print!("{}", operator_cli::version_text());
            return Ok(());
        }
        OperatorCommand::Global { .. } => {
            unreachable!("global command wrappers are unwrapped before dispatch")
        }
        OperatorCommand::Health { config_path } => {
            println!("{}", operator_cli::health_json(config_path.as_deref())?);
            return Ok(());
        }
        OperatorCommand::Readiness { config_path } => {
            println!("{}", operator_cli::readiness_json(config_path.as_deref())?);
            return Ok(());
        }
        OperatorCommand::Diagnostics { config_path } => {
            let diagnostics = if verbosity > 0 {
                operator_cli::diagnostics_json_with_cli_metadata(config_path.as_deref(), verbosity)?
            } else {
                operator_cli::diagnostics_json(config_path.as_deref())?
            };
            println!("{diagnostics}");
            return Ok(());
        }
        OperatorCommand::Metrics { config_path } => {
            print!("{}", operator_cli::metrics_text(config_path.as_deref())?);
            return Ok(());
        }
        OperatorCommand::OpsReportHealth {
            config_path,
            bucket,
        } => {
            println!(
                "{}",
                operator_cli::ops_report_health_json(config_path.as_deref(), bucket.as_deref(),)?
            );
            return Ok(());
        }
        OperatorCommand::OpsReportConfig {
            config_path,
            bucket,
        } => {
            println!(
                "{}",
                operator_cli::ops_report_config_json(config_path.as_deref(), bucket.as_deref(),)?
            );
            return Ok(());
        }
        OperatorCommand::OpsReportAdminSurfaces {
            config_path,
            bucket,
        } => {
            println!(
                "{}",
                operator_cli::ops_report_admin_surfaces_json(
                    config_path.as_deref(),
                    bucket.as_deref(),
                )?
            );
            return Ok(());
        }
        OperatorCommand::OpsReportIncident {
            config_path,
            bucket,
            incident_type,
        } => {
            println!(
                "{}",
                operator_cli::ops_report_incident_json(
                    config_path.as_deref(),
                    bucket.as_deref(),
                    &incident_type,
                )?
            );
            return Ok(());
        }
        OperatorCommand::OpsReportEvidenceExport {
            config_path,
            bucket,
        } => {
            println!(
                "{}",
                operator_cli::ops_report_evidence_export_json(
                    config_path.as_deref(),
                    bucket.as_deref(),
                )?
            );
            return Ok(());
        }
        OperatorCommand::UiBrowserManifest => {
            println!("{}", operator_cli::ui_browser_manifest_json());
            return Ok(());
        }
        OperatorCommand::UiBrowserHtml => {
            print!("{}", operator_cli::ui_browser_html());
            return Ok(());
        }
        OperatorCommand::UiBrowserCss => {
            print!("{}", operator_cli::ui_browser_css());
            return Ok(());
        }
        OperatorCommand::UiBrowserJs => {
            print!("{}", operator_cli::ui_browser_js());
            return Ok(());
        }
        OperatorCommand::ConfigValidate { config_path } => {
            println!("{}", operator_cli::validate_config_path(&config_path)?);
            return Ok(());
        }
        OperatorCommand::AuditExport { config_path } => {
            print!("{}", operator_cli::audit_export(config_path.as_deref())?);
            return Ok(());
        }
        OperatorCommand::ReplicationStatus { config_path } => {
            println!(
                "{}",
                operator_cli::replication_status_json(config_path.as_deref())?
            );
            return Ok(());
        }
        OperatorCommand::S3Serve {
            bind,
            console_bind,
            config_path,
            principal,
            access_key_id,
            secret_access_key,
            storage,
        } => {
            let storage = match storage {
                S3ServeStorage::Filesystem { data_dir } => s3_listener::S3StorageMode::Filesystem {
                    data_dir: data_dir.into(),
                },
                S3ServeStorage::InMemory => s3_listener::S3StorageMode::InMemory,
            };
            return Ok(s3_listener::serve(s3_listener::S3ServeOptions {
                bind,
                console_bind,
                config: operator_cli::load_runtime_config(config_path.as_deref())?,
                principal,
                access_key_id,
                secret_access_key,
                storage,
            })?);
        }
        OperatorCommand::PolicyExplain(args) => {
            println!("{}", operator_tooling::policy_explain_json(&args)?);
            return Ok(());
        }
        OperatorCommand::PolicySimulate(args) => {
            println!("{}", operator_tooling::policy_simulate_json(&args)?);
            return Ok(());
        }
        OperatorCommand::PolicyAnalyze => {
            println!("{}", operator_tooling::policy_analyze_json()?);
            return Ok(());
        }
        OperatorCommand::AuthReportIdentityProviders => {
            println!(
                "{}",
                operator_tooling::auth_report_identity_providers_json()?
            );
            return Ok(());
        }
        OperatorCommand::AuthReportCredentials => {
            println!("{}", operator_tooling::auth_report_credentials_json()?);
            return Ok(());
        }
        OperatorCommand::AuthReportTemporaryCredentials => {
            println!(
                "{}",
                operator_tooling::auth_report_temporary_credentials_json()?
            );
            return Ok(());
        }
        OperatorCommand::AuthRoleAssign {
            principal,
            role,
            scope,
        } => {
            println!(
                "{}",
                operator_tooling::auth_role_assign_json(&principal, role, &scope)?
            );
            return Ok(());
        }
        OperatorCommand::AuthRoleList { principal } => {
            println!("{}", operator_tooling::auth_role_list_json(&principal)?);
            return Ok(());
        }
        OperatorCommand::AuthKeyRotate {
            principal,
            old_access_key_id,
            new_access_key_id,
            secret_access_key,
        } => {
            println!(
                "{}",
                operator_tooling::auth_key_rotate_json(
                    &principal,
                    &old_access_key_id,
                    &new_access_key_id,
                    &secret_access_key,
                )?
            );
            return Ok(());
        }
        OperatorCommand::AuthKeyRevoke {
            principal,
            access_key_id,
        } => {
            println!(
                "{}",
                operator_tooling::auth_key_revoke_json(&principal, &access_key_id)?
            );
            return Ok(());
        }
        OperatorCommand::AuthKeyReportLeaked {
            principal,
            access_key_id,
        } => {
            println!(
                "{}",
                operator_tooling::auth_key_report_leaked_json(&principal, &access_key_id)?
            );
            return Ok(());
        }
        OperatorCommand::Conformance { target } => {
            bucketwarden_demo::print_conformance(target.as_deref())?;
            return Ok(());
        }
        OperatorCommand::Demo(command) => {
            let runtime =
                bucketwarden_server::BucketWarden::new(operator_cli::load_runtime_config(None)?)
                    .map_err(anyhow::Error::from)?;
            return Ok(bucketwarden_demo::run_demo_command(&command, &runtime)?);
        }
        OperatorCommand::DefaultStatus => {
            print_default_status();
            return Ok(());
        }
    }

    unreachable!("all operator commands should be fully handled by match arms");
}

fn print_default_status() {
    println!(
        "{}",
        serde_json::json!({
            "name": "bucketwarden",
            "status": "ready",
            "certified_runtime_slice": "in-memory-0.1.0"
        })
    );
}
