mod claude;
mod keychain;
mod store;
mod switch;

use std::io::{self, Write};

use anyhow::{bail, Result};
use chrono::Utc;
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(
    name = "cloaked",
    about = "Switch between multiple Claude Code accounts seamlessly",
    version
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Snapshot the currently logged-in Claude Code account
    Add,
    /// List all managed accounts
    List,
    /// Switch to an account by number or email
    Switch {
        /// Account number or email address
        account: String,
    },
    /// Remove a managed account
    Remove {
        /// Account number or email address
        account: String,
    },
    /// Show the currently active account
    Status,
}

fn main() {
    let cli = Cli::parse();
    if let Err(e) = run(cli) {
        eprintln!("error: {:#}", e);
        std::process::exit(1);
    }
}

fn run(cli: Cli) -> Result<()> {
    match cli.command {
        Commands::Add => cmd_add(),
        Commands::List => cmd_list(),
        Commands::Switch { account } => cmd_switch(&account),
        Commands::Remove { account } => cmd_remove(&account),
        Commands::Status => cmd_status(),
    }
}

fn cmd_add() -> Result<()> {
    let config = claude::read_claude_config()?;
    let oauth = claude::extract_oauth_account(&config)?;
    let email = claude::extract_email(&oauth)?;
    let uuid = claude::extract_uuid(&oauth).unwrap_or_default();

    let mut s = store::load_store()?;

    if let Some(existing_num) = s.find_by_email(&email) {
        bail!(
            "account {} is already managed as account {}",
            email,
            existing_num
        );
    }

    let account_num = s.next_id();

    let credentials = keychain::read_claude_credentials()?;
    store::save_account_credentials(account_num, &email, &credentials)?;
    store::save_account_config(account_num, &email, &oauth)?;

    s.accounts.insert(
        account_num.to_string(),
        store::AccountInfo {
            email: email.clone(),
            uuid,
            added_at: Utc::now(),
        },
    );
    s.sequence.push(account_num);

    if s.active.is_none() {
        s.active = Some(account_num);
    }

    store::save_store(&s)?;

    println!("Added account {}: {}", account_num, email);
    Ok(())
}

fn cmd_list() -> Result<()> {
    let s = store::load_store()?;

    if s.sequence.is_empty() {
        println!("No accounts managed. Run `cloaked add` to add the current account.");
        return Ok(());
    }

    println!("Accounts:");
    for &num in &s.sequence {
        if let Some(info) = s.accounts.get(&num.to_string()) {
            let active = if s.active == Some(num) { "  (active)" } else { "" };
            println!("  {}: {}{}", num, info.email, active);
        }
    }
    Ok(())
}

fn cmd_switch(input: &str) -> Result<()> {
    let s = store::load_store()?;

    if s.sequence.is_empty() {
        bail!("no accounts managed. Run `cloaked add` first.");
    }

    let target_num = s.find_by_id_or_email(input).ok_or_else(|| {
        anyhow::anyhow!(
            "account '{}' not found. Run `cloaked list` to see available accounts.",
            input
        )
    })?;

    if s.active == Some(target_num) {
        let info = &s.accounts[&target_num.to_string()];
        println!("Already on account {} ({}).", target_num, info.email);
        return Ok(());
    }

    switch::switch_to(target_num)
}

fn cmd_remove(input: &str) -> Result<()> {
    let mut s = store::load_store()?;

    let target_num = s
        .find_by_id_or_email(input)
        .ok_or_else(|| anyhow::anyhow!("account '{}' not found", input))?;

    let info = s.accounts[&target_num.to_string()].clone();

    print!("Remove account {} ({})? [y/N] ", target_num, info.email);
    io::stdout().flush()?;

    let mut answer = String::new();
    io::stdin().read_line(&mut answer)?;

    if answer.trim().to_lowercase() != "y" {
        println!("Aborted.");
        return Ok(());
    }

    store::delete_account_credentials(target_num, &info.email)?;
    store::delete_account_config(target_num, &info.email)?;

    s.accounts.remove(&target_num.to_string());
    s.sequence.retain(|&n| n != target_num);

    let was_active = s.active == Some(target_num);
    if was_active {
        s.active = None;
    }

    store::save_store(&s)?;

    println!("Removed account {} ({}).", target_num, info.email);
    if was_active {
        println!(
            "Warning: this was the active account. Log in to Claude Code manually to resume."
        );
    }

    Ok(())
}

fn cmd_status() -> Result<()> {
    let s = store::load_store()?;

    match s.active {
        None => println!("cloaked: no active account"),
        Some(active_num) => {
            if let Some(info) = s.accounts.get(&active_num.to_string()) {
                println!("cloaked active: account {} ({})", active_num, info.email);
            }
        }
    }

    // Cross-reference with what's actually in ~/.claude.json right now.
    match claude::read_claude_config() {
        Ok(config) => match claude::extract_oauth_account(&config) {
            Ok(oauth) => match claude::extract_email(&oauth) {
                Ok(email) => {
                    let matches = s
                        .active
                        .and_then(|n| s.accounts.get(&n.to_string()))
                        .map(|info| info.email.eq_ignore_ascii_case(&email))
                        .unwrap_or(false);

                    println!("~/.claude.json:  {}", email);

                    if !matches {
                        println!(
                            "Warning: active account in cloaked does not match ~/.claude.json.\n\
                             Run `cloaked switch <n>` to re-sync."
                        );
                    }
                }
                Err(_) => println!("~/.claude.json:  (could not read email)"),
            },
            Err(_) => println!("~/.claude.json:  (no oauthAccount — not logged in?)"),
        },
        Err(_) => println!("~/.claude.json:  (not found)"),
    }

    Ok(())
}
