pg-api 0.1.0

A high-performance PostgreSQL REST API driver with rate limiting, connection pooling, and observability
use anyhow::Result;
use std::io::{self, Write};
use crate::introspection::{introspect_postgres, introspect_with_sudo, PostgresIntrospection};
use crate::models::{Account, DatabaseAccess, AccountRole, Permission};
use chrono::Utc;
use uuid::Uuid;

pub async fn run_setup() -> Result<()> {
    println!("🚀 Welcome to pg-api setup!");
    println!("This wizard will help you configure your PostgreSQL API access.\n");

    // Step 1: Try to connect to PostgreSQL
    let introspection = connect_to_postgres().await?;
    
    println!("\n✅ Successfully connected to PostgreSQL {}", extract_version(&introspection.version));
    println!("Found {} databases and {} users", introspection.databases.len(), introspection.users.len());

    // Step 2: Show existing databases and users
    display_introspection(&introspection);

    // Step 3: Ask if user wants to create a new superuser or use existing
    let accounts = configure_accounts(&introspection).await?;

    // Step 4: Save configuration
    save_configuration(accounts).await?;

    let config_dir = std::env::var("CONFIG_DIR").unwrap_or_else(|_| "config".to_string());
    println!("\n✅ Configuration saved to {}/accounts.json", config_dir);
    println!("You can now start pg-api with: cargo run");

    Ok(())
}

async fn connect_to_postgres() -> Result<PostgresIntrospection> {
    println!("Attempting to connect to PostgreSQL...");
    
    // First try with sudo
    match introspect_with_sudo().await {
        Ok(intro) => return Ok(intro),
        Err(_) => {
            println!("Could not connect using sudo. Let's try with connection details.");
        }
    }

    // Ask for connection details
    loop {
        let host = prompt("PostgreSQL host (default: localhost): ", "localhost");
        let port = prompt("PostgreSQL port (default: 5432): ", "5432");
        let username = prompt("PostgreSQL username: ", "");
        let password = prompt_password("PostgreSQL password: ")?;
        let database = prompt("Database to connect (default: postgres): ", "postgres");

        let connection_string = format!(
            "host={host} port={port} user={username} password={password} dbname={database}",
        );

        match introspect_postgres(&connection_string).await {
            Ok(intro) => return Ok(intro),
            Err(e) => {
                println!("❌ Connection failed: {e}");
                if !confirm("Try again?")? {
                    anyhow::bail!("Setup cancelled");
                }
            }
        }
    }
}

fn display_introspection(intro: &PostgresIntrospection) {
    println!("\n📊 Current PostgreSQL state:");
    
    println!("\nDatabases:");
    println!("{:<20} {:<15} {:<10} {:<15}", "Name", "Owner", "Encoding", "Size");
    println!("{}", "-".repeat(60));
    for db in &intro.databases {
        println!("{:<20} {:<15} {:<10} {:<15}", 
            db.name, 
            db.owner, 
            db.encoding,
            db.size.as_ref().unwrap_or(&"N/A".to_string())
        );
    }

    println!("\nUsers:");
    println!("{:<20} {:<10} {:<10} {:<10}", "Username", "Superuser", "Create DB", "Create Role");
    println!("{}", "-".repeat(60));
    for user in &intro.users {
        println!("{:<20} {:<10} {:<10} {:<10}", 
            user.username,
            if user.is_superuser { "Yes" } else { "No" },
            if user.can_create_db { "Yes" } else { "No" },
            if user.can_create_role { "Yes" } else { "No" }
        );
    }
}

async fn configure_accounts(intro: &PostgresIntrospection) -> Result<Vec<Account>> {
    let mut accounts = Vec::new();
    
    println!("\n🔧 Account Configuration");
    
    // Always create a superuser account
    if confirm("Create a superuser API account?")? {
        let account = create_superuser_account(intro)?;
        accounts.push(account);
    }

    // Ask about exposing existing users
    if confirm("\nWould you like to expose existing PostgreSQL users through the API?")? {
        for user in &intro.users {
            if user.username == "postgres" {
                continue; // Skip the postgres user itself
            }
            
            println!("\nUser: {}", user.username);
            if confirm(&format!("Expose user '{}' through API?", user.username))? {
                let account = create_user_account(user, intro)?;
                accounts.push(account);
            }
        }
    }

    Ok(accounts)
}

fn create_superuser_account(intro: &PostgresIntrospection) -> Result<Account> {
    println!("\nConfiguring superuser account:");
    
    let name = prompt("Account name (default: postgres-superuser): ", "postgres-superuser");
    let api_key = generate_api_key();
    
    println!("Generated API key: {api_key}");
    println!("⚠️  Save this API key securely, it won't be shown again!");

    let mut databases = Vec::new();
    
    // Ask which databases to expose
    println!("\nWhich databases should this superuser have access to?");
    for db in &intro.databases {
        if confirm(&format!("Include database '{}'?", db.name))? {
            let username = prompt(&format!("PostgreSQL username for database '{}': ", db.name), "postgres");
            let password = prompt_password(&format!("PostgreSQL password for user '{username}': "))?;
            
            databases.push(DatabaseAccess {
                database: db.name.clone(),
                username,
                password,
                permissions: vec![Permission::All],
            });
        }
    }

    Ok(Account {
        id: format!("acc_{}", Uuid::new_v4().to_string().replace("-", "").chars().take(12).collect::<String>()),
        name: name.clone(),
        api_key,
        instance_id: "default".to_string(),
        databases,
        role: AccountRole::Owner,
        created_at: Utc::now(),
        last_used: Utc::now(),
        rate_limit: 0, // No rate limit for superuser
        max_connections: 100,
        notes: Some(format!("Superuser account for {}", name)),
    })
}

fn create_user_account(user: &crate::introspection::UserInfo, intro: &PostgresIntrospection) -> Result<Account> {
    println!("\nConfiguring account for user '{}':", user.username);
    
    let name = prompt(&format!("Account name (default: {}): ", user.username), &user.username);
    let api_key = generate_api_key();
    
    println!("Generated API key: {api_key}");
    
    let mut databases = Vec::new();
    
    // Show databases this user has access to
    println!("\nDatabases accessible by this user:");
    for db_name in &user.databases {
        if let Some(db) = intro.databases.iter().find(|d| &d.name == db_name) {
            if confirm(&format!("Include database '{}'?", db.name))? {
                let password = prompt_password(&format!("PostgreSQL password for user '{}': ", user.username))?;
                
                databases.push(DatabaseAccess {
                    database: db.name.clone(),
                    username: user.username.clone(),
                    password,
                    permissions: if user.is_superuser { 
                        vec![Permission::All] 
                    } else { 
                        vec![Permission::Select, Permission::Insert, Permission::Update, Permission::Delete] 
                    },
                });
            }
        }
    }
    
    let rate_limit: u32 = prompt("Rate limit per minute (default: 1000): ", "1000").parse().unwrap_or(1000);
    let max_connections: u32 = prompt("Max connections (default: 10): ", "10").parse().unwrap_or(10);

    Ok(Account {
        id: format!("acc_{}", Uuid::new_v4().to_string().replace("-", "").chars().take(12).collect::<String>()),
        name,
        api_key,
        instance_id: "default".to_string(),
        databases,
        role: if user.is_superuser { AccountRole::Owner } else { AccountRole::Developer },
        created_at: Utc::now(),
        last_used: Utc::now(),
        rate_limit,
        max_connections,
        notes: Some(format!("Account for user {}", user.username)),
    })
}

async fn save_configuration(accounts: Vec<Account>) -> Result<()> {
    let json = serde_json::to_string_pretty(&accounts)?;
    
    // Get config directory from environment or use default
    let config_dir = std::env::var("CONFIG_DIR").unwrap_or_else(|_| "config".to_string());
    let config_path = std::path::PathBuf::from(&config_dir).join("accounts.json");
    
    tokio::fs::create_dir_all(&config_dir).await?;
    tokio::fs::write(config_path, json).await?;
    Ok(())
}

fn generate_api_key() -> String {
    // Gerar 2 UUIDs para ter 256 bits de entropia
    format!("{}{}", 
        Uuid::new_v4().simple(), 
        Uuid::new_v4().simple()
    )
}

fn prompt(message: &str, default: &str) -> String {
    print!("{message}");
    io::stdout().flush().unwrap();
    
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    
    let trimmed = input.trim();
    if trimmed.is_empty() && !default.is_empty() {
        default.to_string()
    } else {
        trimmed.to_string()
    }
}

fn prompt_password(message: &str) -> Result<String> {
    print!("{message}");
    io::stdout().flush()?;
    
    // In a real implementation, you'd want to hide the password input
    // For now, we'll just read it normally
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    
    Ok(input.trim().to_string())
}

fn confirm(message: &str) -> Result<bool> {
    loop {
        print!("{message} (y/n): ");
        io::stdout().flush()?;
        
        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        
        match input.trim().to_lowercase().as_str() {
            "y" | "yes" => return Ok(true),
            "n" | "no" => return Ok(false),
            _ => println!("Please enter 'y' or 'n'"),
        }
    }
}

fn extract_version(version: &str) -> &str {
    version.split_whitespace()
        .find(|s| s.starts_with("PostgreSQL"))
        .and_then(|s| s.split_whitespace().nth(1))
        .unwrap_or("Unknown")
}