Documentation
use anchor_cli::config::{Config, ConfigOverride};
use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig;
use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
use anchor_client::solana_sdk::pubkey::Pubkey;
use anchor_client::solana_sdk::signature::{read_keypair_file, Keypair};
use anchor_client::solana_sdk::signer::Signer;
use anchor_client::solana_sdk::system_program;
use anchor_client::Client;
use anyhow::{anyhow, Result};
use std::rc::Rc;
use world::{accounts, instruction, Registry, World, ID};

fn setup_client(cfg_override: &ConfigOverride) -> Result<(Client<Rc<Keypair>>, Keypair)> {
    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
    let wallet_path = cfg.provider.wallet.clone();

    let payer = read_keypair_file(wallet_path.to_string())
        .map_err(|e| anyhow!("Failed to read keypair file: {}", e))?;

    let payer_for_client =
        Keypair::from_bytes(&payer.to_bytes()).expect("Failed to create Keypair from bytes");

    let client = Client::new_with_options(
        cfg.provider.cluster.clone(),
        Rc::new(payer_for_client),
        CommitmentConfig::confirmed(),
    );
    Ok((client, payer))
}

fn parse_pubkey(input: &str, error_message: &str) -> Result<Pubkey> {
    input
        .parse::<Pubkey>()
        .map_err(|_| anyhow!(error_message.to_string()))
}

pub async fn create_registry(cfg_override: &ConfigOverride) -> Result<()> {
    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let (registry_pda, _) = Pubkey::find_program_address(&[Registry::seed()], &ID);

    let signature = program
        .request()
        .accounts(accounts::InitializeRegistry {
            registry: registry_pda,
            payer: payer.pubkey(),
            system_program: system_program::ID,
        })
        .args(instruction::InitializeRegistry {})
        .signer(payer)
        .send()
        .await?;

    println!(
        "New registry {} created with signature {}",
        registry_pda, signature
    );

    Ok(())
}

pub async fn create_world(cfg_override: &ConfigOverride) -> Result<()> {
    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let (registry_pda, _) = Pubkey::find_program_address(&[Registry::seed()], &ID);

    let registry_account: Registry = program.account(registry_pda).await?;
    let world_id = registry_account.worlds;

    let (world_pda, _) =
        Pubkey::find_program_address(&[World::seed(), &world_id.to_be_bytes()], &ID);

    let signature = program
        .request()
        .accounts(accounts::InitializeNewWorld {
            payer: payer.pubkey(),
            world: world_pda,
            registry: registry_pda,
            system_program: system_program::ID,
        })
        .args(instruction::InitializeNewWorld {})
        .signer(payer)
        .send()
        .await?;

    println!(
        "New world created {} with world id {}. Transaction signature {}",
        world_pda, world_id, signature
    );

    Ok(())
}

pub async fn authorize(
    cfg_override: &ConfigOverride,
    world: String,
    new_authority: String,
) -> Result<()> {
    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
    let new_authority_pubkey = parse_pubkey(&new_authority, "Invalid new authority public key")?;

    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let world_account = program.account::<World>(world_pubkey).await?;
    let world_id = world_account.id;
    let signature = program
        .request()
        .accounts(accounts::AddAuthority {
            authority: payer.pubkey(),
            new_authority: new_authority_pubkey,
            system_program: system_program::ID,
            world: world_pubkey,
        })
        .args(instruction::AddAuthority { world_id })
        .signer(payer)
        .send()
        .await?;

    println!(
        "Authority {} added to world {} with signature {}",
        new_authority, world, signature
    );

    Ok(())
}

pub async fn deauthorize(
    cfg_override: &ConfigOverride,
    world: String,
    authority_to_delete: String,
) -> Result<()> {
    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
    let authority_to_delete_pubkey =
        parse_pubkey(&authority_to_delete, "Invalid authority public key")?;

    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let world_account = program.account::<World>(world_pubkey).await?;
    let world_id = world_account.id;
    let signature = program
        .request()
        .accounts(accounts::RemoveAuthority {
            authority: payer.pubkey(),
            authority_to_delete: authority_to_delete_pubkey,
            system_program: system_program::ID,
            world: world_pubkey,
        })
        .args(instruction::RemoveAuthority { world_id })
        .signer(payer)
        .send_with_spinner_and_config(RpcSendTransactionConfig {
            skip_preflight: true,
            ..RpcSendTransactionConfig::default()
        })
        .await?;

    println!(
        "Authority {} removed from world {} with signature {}",
        authority_to_delete, world, signature
    );

    Ok(())
}

pub async fn approve_system(
    cfg_override: &ConfigOverride,
    world: String,
    system_to_approve: String,
) -> Result<()> {
    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
    let system_to_approve_pubkey = parse_pubkey(&system_to_approve, "Invalid system public key")?;

    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let signature = program
        .request()
        .accounts(accounts::ApproveSystem {
            authority: payer.pubkey(),
            system: system_to_approve_pubkey,
            world: world_pubkey,
            system_program: system_program::ID,
        })
        .args(instruction::ApproveSystem {})
        .signer(payer)
        .send()
        .await?;

    println!(
        "System {} approved for world {} with signature {}",
        system_to_approve, world, signature
    );

    Ok(())
}

pub async fn remove_system(
    cfg_override: &ConfigOverride,
    world: String,
    system_to_remove: String,
) -> Result<()> {
    let world_pubkey = parse_pubkey(&world, "Invalid world public key")?;
    let system_to_remove_pubkey = parse_pubkey(&system_to_remove, "Invalid system public key")?;

    let (client, payer) = setup_client(cfg_override)?;
    let program = client.program(ID)?;

    let signature = program
        .request()
        .accounts(accounts::RemoveSystem {
            authority: payer.pubkey(),
            system: system_to_remove_pubkey,
            world: world_pubkey,
            system_program: system_program::ID,
        })
        .args(instruction::RemoveSystem {})
        .signer(payer)
        .send()
        .await?;

    println!(
        "System {} removed from world {} with signature {}",
        system_to_remove, world, signature
    );

    Ok(())
}