mod program_test;
use {
    mpl_token_metadata::accounts::{MasterEdition, Metadata},
    program_test::StablebondProgramTest,
    solana_program_test::{tokio, ProgramTestContext},
    solana_sdk::{
        signature::{Keypair, Signer},
        transaction::Transaction,
    },
    spl_associated_token_account::get_associated_token_address,
    stablebond_sdk::{
        find_bond_pda, find_config_pda, find_delegate_pda, find_payment_feed_pda,
        instructions::{
            InitializeBond, InitializeBondInstructionArgs, InitializeConfig,
            InitializeConfigInstructionArgs, InitializeDelegate, InitializePaymentFeed,
            InitializePaymentFeedInstructionArgs, UpdateBondPaymentFeed,
        },
        types::{Descriminator, OracleSetup, PaymentFeedInfo, PaymentFeedType},
    },
};
struct ExecuteTestContext {
    context: ProgramTestContext,
    admin: Keypair,
    bond_mint: Keypair,
}

async fn setup() -> ExecuteTestContext {
    let pt = StablebondProgramTest::start_new().await;
    let admin = pt.admin;
    let bond_mint = pt.bond_mints[0].insecure_clone();
    let nft_collection_mint = pt.nft_collection_mint;

    let initialize_config_ix_args = InitializeConfigInstructionArgs {
        oracle: OracleSetup::Stub,
        usdc_mxn_payment_feed: pt.usdc_mxn_payment_feed,
        usdc_usd_payment_feed: pt.usdc_usd_payment_feed,
    };

    let initialize_config_ix = InitializeConfig {
        admin_wallet: admin.pubkey(),
        config_account: find_config_pda().0,
        config_token_account: get_associated_token_address(
            &find_config_pda().0,
            &nft_collection_mint.pubkey(),
        ),
        mint_account: nft_collection_mint.pubkey(),
        metadata_account: Metadata::find_pda(&nft_collection_mint.pubkey()).0,
        master_edition_account: MasterEdition::find_pda(&nft_collection_mint.pubkey()).0,
        usdc_mxn_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcMxn).0,
        usdc_usd_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).0,
        token_program: spl_token::id(),
        associated_token_program: spl_associated_token_account::id(),
        metadata_program: mpl_token_metadata::ID,
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_config_ix_args);

    let initialize_delegate_ix = InitializeDelegate {
        config_account: find_config_pda().0,
        admin_wallet: admin.pubkey(),
        delegate_wallet: admin.pubkey(),
        delegate_account: find_delegate_pda(admin.pubkey()).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let ixs = &[initialize_config_ix, initialize_delegate_ix];

    let mut context = pt.context;
    let transaction = Transaction::new_signed_with_payer(
        ixs,
        Some(&admin.pubkey()),
        &[&admin, &nft_collection_mint],
        context.last_blockhash,
    );

    assert!(context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let payment_feed_info = PaymentFeedInfo {
        discriminator: Descriminator::PaymentFeed,
        version: 0,
        payment_feed_type: PaymentFeedType::SwitchboardUsdcMxn,
        payment_mint: Keypair::new().pubkey(),
        payment_decimals: 6,
        base_price_feed: Keypair::new().pubkey(),
        quote_price_feed: Keypair::new().pubkey(),
    };

    let initialize_payment_feed_args = InitializePaymentFeedInstructionArgs { payment_feed_info };

    let initialize_payment_feed_ix = InitializePaymentFeed {
        delegate_wallet: admin.pubkey(),
        delegate_account: find_delegate_pda(admin.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcMxn).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_payment_feed_args);

    let transaction: Transaction = Transaction::new_signed_with_payer(
        &[initialize_payment_feed_ix],
        Some(&admin.pubkey()),
        &[&admin],
        context.last_blockhash,
    );

    assert!(context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let payment_feed_info = PaymentFeedInfo {
        discriminator: Descriminator::PaymentFeed,
        version: 0,
        payment_feed_type: PaymentFeedType::SwitchboardUsdcUsd,
        payment_mint: Keypair::new().pubkey(),
        payment_decimals: 6,
        base_price_feed: Keypair::new().pubkey(),
        quote_price_feed: Keypair::new().pubkey(),
    };

    let initialize_payment_feed_args = InitializePaymentFeedInstructionArgs { payment_feed_info };

    let initialize_payment_feed_ix = InitializePaymentFeed {
        delegate_wallet: admin.pubkey(),
        delegate_account: find_delegate_pda(admin.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcUsd).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_payment_feed_args);

    let transaction: Transaction = Transaction::new_signed_with_payer(
        &[initialize_payment_feed_ix],
        Some(&admin.pubkey()),
        &[&admin],
        context.last_blockhash,
    );

    assert!(context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    ExecuteTestContext {
        context,
        admin,
        bond_mint,
    }
}

#[tokio::test]
async fn update_bond_payment_feed_as_delegate_pyth_usd_to_switchboard_usd() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::UsdcUsd,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcUsd).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());
}

#[tokio::test]
async fn update_bond_payment_feed_as_delegate_pyth_mxn_to_switchboard_mxn() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::UsdcMxn,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcMxn).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());
}

#[tokio::test]
async fn update_bond_payment_feed_as_delegate_stub_to_switchboard_usd() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::Stub,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcUsd).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());
}

#[tokio::test]
async fn fail_update_bond_payment_feed_as_delegate_pyth_usd_to_switchboard_mxn() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::UsdcUsd,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcMxn).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_err());
}

#[tokio::test]
async fn fail_update_bond_payment_feed_as_delegate_pyth_mxn_to_switchboard_usd() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::UsdcMxn,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::SwitchboardUsdcUsd).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_err());
}

#[tokio::test]
async fn fail_update_bond_payment_feed_as_delegate_switchboard_usd_to_pyth_mxn() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::SwitchboardUsdcUsd,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcMxn).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_err());
}

#[tokio::test]
async fn fail_update_bond_payment_feed_as_delegate_switchboard_mxn_to_pyth_usd() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::SwitchboardUsdcMxn,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_err());
}

#[tokio::test]
async fn fail_update_bond_payment_feed_as_delegate_pyth_mxn_to_stub() {
    let mut et = setup().await;

    let initialize_bond_ix_args = InitializeBondInstructionArgs {
        decimals: 6,
        name: "Test Mint".to_string(),
        symbol: "TM".to_string(),
        uri: "https://test.com".to_string(),
        payment_feed_type: PaymentFeedType::UsdcMxn,
        cutoff_in_seconds: 60 * 60 * 24,
    };

    let initialize_bond_ix = InitializeBond {
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        delegate_wallet: et.admin.pubkey(),
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        mint_account: et.bond_mint.pubkey(),
        metadata_account: Metadata::find_pda(&et.bond_mint.pubkey()).0,
        token2022_program: spl_token_2022::id(),
        metadata_program: mpl_token_metadata::ID,
        sysvar_instructions: solana_program::sysvar::instructions::id(),
        system_program: solana_program::system_program::id(),
    }
    .instruction(initialize_bond_ix_args);

    let transaction = Transaction::new_signed_with_payer(
        &[initialize_bond_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin, &et.bond_mint],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_ok());

    let update_bond_payment_feed_ix = UpdateBondPaymentFeed {
        delegate_wallet: et.admin.pubkey(),
        delegate_account: find_delegate_pda(et.admin.pubkey()).0,
        bond_account: find_bond_pda(et.bond_mint.pubkey()).0,
        payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0,
        system_program: solana_program::system_program::id(),
    }
    .instruction();

    let transaction = Transaction::new_signed_with_payer(
        &[update_bond_payment_feed_ix],
        Some(&et.admin.pubkey()),
        &[&et.admin],
        et.context.last_blockhash,
    );

    assert!(et
        .context
        .banks_client
        .process_transaction(transaction)
        .await
        .is_err());
}
