use solana_program::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address;
use steel::*;

use crate::{
    consts::{AUCTION, BOARD, MINT_ADDRESS, SOL_MINT, TREASURY_ADDRESS},
    instruction::{self, *},
    state::*,
};

pub fn log(signer: Pubkey, msg: &[u8]) -> Instruction {
    let mut data = Log {}.to_bytes();
    data.extend_from_slice(msg);
    Instruction {
        program_id: crate::ID,
        accounts: vec![AccountMeta::new(signer, true)],
        data: data,
    }
}

pub fn program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> {
    // Derive Board PDA to use as signer for log instruction
    let (board_address, _) = board_pda();
    invoke_signed(&log(board_address, msg), accounts, &crate::ID, &[BOARD])
}

/// Log event for auction-based instructions (uses Auction PDA instead of Board)
pub fn auction_program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> {
    // Derive Auction PDA to use as signer for log instruction
    let (auction_address, _) = auction_pda();
    invoke_signed(&log(auction_address, msg), accounts, &crate::ID, &[AUCTION])
}

// let [signer_info, board_info, config_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] = accounts else {

// pub fn initialize(
//     signer: Pubkey,
//     barrel_authority: Pubkey,
//     fee_collector: Pubkey,
//     swap_program: Pubkey,
//     var_address: Pubkey,
//     admin_fee: u64,
// ) -> Instruction {
//     let board_address = board_pda().0;
//     let config_address = config_pda().0;
//     let mint_address = MINT_ADDRESS;
//     let treasury_address = TREASURY_ADDRESS;
//     let treasury_tokens_address = treasury_tokens_address();
//     Instruction {
//         program_id: crate::ID,
//         accounts: vec![
//             AccountMeta::new(signer, true),
//             AccountMeta::new(board_address, false),
//             AccountMeta::new(config_address, false),
//             AccountMeta::new(mint_address, false),
//             AccountMeta::new(treasury_address, false),
//             AccountMeta::new(treasury_tokens_address, false),
//             AccountMeta::new_readonly(system_program::ID, false),
//             AccountMeta::new_readonly(spl_token::ID, false),
//             AccountMeta::new_readonly(spl_associated_token_account::ID, false),
//         ],
//         data: Initialize {
//             barrel_authority: barrel_authority.to_bytes(),
//             fee_collector: fee_collector.to_bytes(),
//             swap_program: swap_program.to_bytes(),
//             var_address: var_address.to_bytes(),
//             admin_fee: admin_fee.to_le_bytes(),
//         }
//         .to_bytes(),
//     }
// }

// let [signer_info, automation_info, executor_info, driller_info, system_program] = accounts else {

/// Set up automation for a driller. If the driller doesn't exist yet, pass a referrer to set it.
/// If a referrer is provided and the driller is new, the referral account must be included.
pub fn automate(
    signer: Pubkey,
    amount: u64,
    deposit: u64,
    executor: Pubkey,
    fee: u64,
    mask: u64,
    strategy: u8,
    reload: bool,
    referrer: Option<Pubkey>,
    pooled: bool,
    is_new_driller: bool,
) -> Instruction {
    let automation_address = automation_pda(signer).0;
    let driller_address = driller_pda(signer).0;
    let referrer_pk = referrer.unwrap_or(Pubkey::default());
    
    let mut accounts = vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(automation_address, false),
            AccountMeta::new(executor, false),
            AccountMeta::new(driller_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
    ];
    
    // Add referral account if referrer is provided and driller is new (for incrementing total_referred)
    if is_new_driller && referrer.is_some() && referrer_pk != Pubkey::default() {
        let referral_address = referral_pda(referrer_pk).0;
        accounts.push(AccountMeta::new(referral_address, false));
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: Automate {
            amount: amount.to_le_bytes(),
            deposit: deposit.to_le_bytes(),
            fee: fee.to_le_bytes(),
            mask: mask.to_le_bytes(),
            strategy: strategy as u8,
            reload: (reload as u64).to_le_bytes(),
            referrer: referrer_pk.to_bytes(),
            pooled: pooled as u8,
        }
        .to_bytes(),
    }
}

/// Claim SOL rewards with single-tier referral system.
/// 
/// If the driller has a referrer, 1.0% of the claim goes to the referrer.
/// 
/// Account structure:
/// - Base: signer, driller, system_program
/// - If driller has referrer (required): [driller_referrer, referral_referrer]
pub fn claim_sol(
    signer: Pubkey,
    referrer_driller: Option<Pubkey>, // Referrer's driller PDA (if driller has referrer)
    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if driller has referrer)
) -> Instruction {
    let driller_address = driller_pda(signer).0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(driller_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
    ];
    
    // Add referrer accounts if provided (required if driller has referrer)
    if let (Some(driller_pubkey), Some(referral_pubkey)) = (referrer_driller, referrer_referral) {
        accounts.push(AccountMeta::new(driller_pubkey, false));
        accounts.push(AccountMeta::new(referral_pubkey, false));
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ClaimSOL {}.to_bytes(),
    }
}

// let [signer_info, driller_info, mint_info, recipient_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =

/// Claim OIL rewards with single-tier referral system.
/// 
/// If the driller has a referrer, 1.0% of the claim goes to the referrer.
/// 
/// Account structure:
/// - Base: signer, driller, mint, recipient, treasury, treasury_tokens, system_program, token_program, associated_token_program
/// - If driller has referrer (required): [driller_referrer, referral_referrer, referral_referrer_oil_ata]
pub fn claim_oil(
    signer: Pubkey,
    referrer_driller: Option<Pubkey>, // Referrer's driller PDA (if driller has referrer)
    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if driller has referrer)
    referrer_referral_oil_ata: Option<Pubkey>, // Referrer's referral OIL ATA (if driller has referrer)
) -> Instruction {
    let driller_address = driller_pda(signer).0;
    let treasury_address = treasury_pda().0;
    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(driller_address, false),
        AccountMeta::new(MINT_ADDRESS, false),
        AccountMeta::new(recipient_address, false),
        AccountMeta::new(treasury_address, false),
        AccountMeta::new(treasury_tokens_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(spl_token::ID, false),
        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
    ];
    
    // Add referrer accounts if provided (required if driller has referrer)
    if let (Some(driller_pubkey), Some(referral_pubkey), Some(oil_ata_pubkey)) = 
        (referrer_driller, referrer_referral, referrer_referral_oil_ata) {
        accounts.push(AccountMeta::new(driller_pubkey, false));
        accounts.push(AccountMeta::new(referral_pubkey, false));
        accounts.push(AccountMeta::new(oil_ata_pubkey, false));
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ClaimOIL {}.to_bytes(),
    }
}


pub fn close(signer: Pubkey, round_id: u64, rent_payer: Pubkey) -> Instruction {
    let board_address = board_pda().0;
    let round_address = round_pda(round_id).0;
    let treasury_address = TREASURY_ADDRESS;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(board_address, false),
            AccountMeta::new(rent_payer, false),
            AccountMeta::new(round_address, false),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: Close {}.to_bytes(),
    }
}

/// Deploy SOL to prospect on squares. Pass a referrer pubkey for new drillers to set up referral.
/// Set `pooled` to true to join the mining pool (rewards shared proportionally).
pub fn deploy(
    signer: Pubkey,
    authority: Pubkey,
    amount: u64,
    round_id: u64,
    squares: [bool; 25],
    referrer: Option<Pubkey>,
    pooled: bool,
) -> Instruction {
    let automation_address = automation_pda(authority).0;
    let board_address = board_pda().0;
    let config_address = config_pda().0;
    let driller_address = driller_pda(authority).0;
    let round_address = round_pda(round_id).0;
    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;

    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
    // that square index is selected (1) or not (0)
    let mut mask: u32 = 0;
    for (i, &square) in squares.iter().enumerate() {
        if square {
            mask |= 1 << i;
        }
    }
    
    // Get referrer bytes (default to zero pubkey if no referrer).
    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
    let referrer_bytes = referrer_pubkey.to_bytes();

    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(authority, false),
        AccountMeta::new(automation_address, false),
        AccountMeta::new(board_address, false),
        AccountMeta::new(config_address, false),
        AccountMeta::new(driller_address, false),
        AccountMeta::new(round_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(crate::ID, false),
        // Entropy accounts.
        AccountMeta::new(entropy_var_address, false),
        AccountMeta::new_readonly(entropy_rng_api::ID, false),
    ];
    
    // Add referral account if referrer is provided (for incrementing total_referred on new drillers).
    if referrer_pubkey != Pubkey::default() {
        let referral_address = referral_pda(referrer_pubkey).0;
        accounts.push(AccountMeta::new(referral_address, false));
    }

    Instruction {
        program_id: crate::ID,
        accounts,
        data: Deploy {
            amount: amount.to_le_bytes(),
            squares: mask.to_le_bytes(),
            referrer: referrer_bytes,
            pooled: if pooled { 1 } else { 0 },
        }
        .to_bytes(),
    }
}

// let [pool, user_source_token, user_destination_token, a_vault, b_vault, a_token_vault, b_token_vault, a_vault_lp_mint, b_vault_lp_mint, a_vault_lp, b_vault_lp, protocol_token_fee, user_key, vault_program, token_program] =

pub fn buyback(signer: Pubkey, swap_accounts: &[AccountMeta], swap_data: &[u8]) -> Instruction {
    let board_address = board_pda().0;
    let config_address = config_pda().0;
    let mint_address = MINT_ADDRESS;
    let treasury_address = TREASURY_ADDRESS;
    let treasury_oil_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
    let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_MINT);
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(board_address, false),
        AccountMeta::new_readonly(config_address, false),
        AccountMeta::new(mint_address, false),
        AccountMeta::new(treasury_address, false),
        AccountMeta::new(treasury_oil_address, false),
        AccountMeta::new(treasury_sol_address, false),
        AccountMeta::new_readonly(spl_token::ID, false),
        AccountMeta::new_readonly(crate::ID, false),
    ];
    for account in swap_accounts.iter() {
        let mut acc_clone = account.clone();
        acc_clone.is_signer = false;
        accounts.push(acc_clone);
    }
    let mut data = Buyback {}.to_bytes();
    data.extend_from_slice(swap_data);
    Instruction {
        program_id: crate::ID,
        accounts,
        data,
    }
}

// let [signer_info, board_info, config_info, fee_collector_info, mint_info, round_info, round_next_info, top_driller_info, treasury_info, treasury_tokens_info, system_program, token_program, oil_program, slot_hashes_sysvar] =

pub fn reset(
    signer: Pubkey,
    fee_collector: Pubkey,
    round_id: u64,
    top_driller: Pubkey,
    var_address: Pubkey,
) -> Instruction {
    let board_address = board_pda().0;
    let config_address = config_pda().0;
    let mint_address = MINT_ADDRESS;
    let round_address = round_pda(round_id).0;
    let round_next_address = round_pda(round_id + 1).0;
    let top_driller_address = driller_pda(top_driller).0;
    let treasury_address = TREASURY_ADDRESS;
    let treasury_tokens_address = treasury_tokens_address();
    let pool_address = pool_pda().0;
    let mint_authority_address = oil_mint_api::state::authority_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(board_address, false),
            AccountMeta::new(config_address, false),
            AccountMeta::new(fee_collector, false),
            AccountMeta::new(mint_address, false),
            AccountMeta::new(round_address, false),
            AccountMeta::new(round_next_address, false),
            AccountMeta::new(top_driller_address, false),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new(pool_address, false),
            AccountMeta::new(treasury_tokens_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(spl_token::ID, false),
            AccountMeta::new_readonly(crate::ID, false),
            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
            // Entropy accounts.
            AccountMeta::new(var_address, false),
            AccountMeta::new_readonly(entropy_rng_api::ID, false),
            // Mint accounts.
            AccountMeta::new(mint_authority_address, false),
            AccountMeta::new_readonly(oil_mint_api::ID, false),
        ],
        data: Reset {}.to_bytes(),
    }
}
    
// let [signer_info, automation_info, board_info, driller_info, round_info, treasury_info, system_program] =

pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
    let driller_address = driller_pda(authority).0;
    let board_address = board_pda().0;
    let round_address = round_pda(round_id).0;
    let treasury_address = TREASURY_ADDRESS;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(board_address, false),
            AccountMeta::new(driller_address, false),
            AccountMeta::new(round_address, false),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: Checkpoint {}.to_bytes(),
    }
}

pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
    let config_address = config_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(config_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: SetAdmin {
            admin: admin.to_bytes(),
        }
        .to_bytes(),
    }
}

pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
    let config_address = config_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(config_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: SetAdminFee {
            admin_fee: admin_fee.to_le_bytes(),
        }
        .to_bytes(),
    }
}

pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
    let config_address = config_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(config_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: SetFeeCollector {
            fee_collector: fee_collector.to_bytes(),
        }
        .to_bytes(),
    }
}

pub fn set_auction(
    signer: Pubkey,
    halving_period_seconds: u64,
    last_halving_time: u64,
    base_mining_rates: [u64; 4],
    auction_duration_seconds: u64,
    starting_prices: [u64; 4],
    min_pool_contribution: u64,
    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
) -> Instruction {
    let config_address = config_pda().0;
    let auction_address = auction_pda().0;
    
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new_readonly(config_address, false),
            AccountMeta::new(auction_address, false),
        ],
        data: SetAuction {
            halving_period_seconds: halving_period_seconds.to_le_bytes(),
            last_halving_time: last_halving_time.to_le_bytes(),
            base_mining_rates: [
                base_mining_rates[0].to_le_bytes(),
                base_mining_rates[1].to_le_bytes(),
                base_mining_rates[2].to_le_bytes(),
                base_mining_rates[3].to_le_bytes(),
            ],
            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
            starting_prices: [
                starting_prices[0].to_le_bytes(),
                starting_prices[1].to_le_bytes(),
                starting_prices[2].to_le_bytes(),
                starting_prices[3].to_le_bytes(),
            ],
            min_pool_contribution: min_pool_contribution.to_le_bytes(),
            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
        }
        .to_bytes(),
    }
}

// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =

pub fn deposit(signer: Pubkey, payer: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
    let mint_address = MINT_ADDRESS;
    let stake_address = stake_pda_with_id(signer, stake_id).0;
    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
    let sender_address = get_associated_token_address(&signer, &MINT_ADDRESS);
    let pool_address = pool_pda().0;
    let pool_tokens_address = pool_tokens_address();
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(payer, true),
            AccountMeta::new(mint_address, false),
            AccountMeta::new(sender_address, false),
            AccountMeta::new(stake_address, false),
            AccountMeta::new(stake_tokens_address, false),
            AccountMeta::new(pool_address, false),
            AccountMeta::new(pool_tokens_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(spl_token::ID, false),
            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
        ],
        data: Deposit {
            amount: amount.to_le_bytes(),
            lock_duration_days: lock_duration_days.to_le_bytes(),
            stake_id: stake_id.to_le_bytes(),
        }
        .to_bytes(),
    }
}

// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =

pub fn withdraw(signer: Pubkey, amount: u64, stake_id: u64) -> Instruction {
    let stake_address = stake_pda_with_id(signer, stake_id).0;
    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
    let mint_address = MINT_ADDRESS;
    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
    let pool_address = pool_pda().0;
    let pool_tokens_address = pool_tokens_address();
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(mint_address, false),
            AccountMeta::new(recipient_address, false),
            AccountMeta::new(stake_address, false),
            AccountMeta::new(stake_tokens_address, false),
            AccountMeta::new(pool_address, false),
            AccountMeta::new(pool_tokens_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(spl_token::ID, false),
            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
        ],
        data: Withdraw {
            amount: amount.to_le_bytes(),
            stake_id: stake_id.to_le_bytes(),
        }
        .to_bytes(),
    }
}

// let [signer_info, automation_info, driller_info, system_program] = accounts else {

/// Reload SOL from driller account to automation balance with single-tier referral system.
/// 
/// If the driller has a referrer, 1.0% of the claim goes to the referrer.
/// 
/// Account structure:
/// - Base: signer, automation, driller, system_program
/// - If driller has referrer (required): [driller_referrer, referral_referrer]
pub fn reload_sol(
    signer: Pubkey,
    authority: Pubkey,
    referrer_driller: Option<Pubkey>,
    referrer_referral: Option<Pubkey>,
) -> Instruction {
    let automation_address = automation_pda(authority).0;
    let driller_address = driller_pda(authority).0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(automation_address, false),
        AccountMeta::new(driller_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
    ];
    
    // Add referral accounts if provided (required when driller has referrer)
    if let (Some(driller_ref), Some(referral_ref)) = (referrer_driller, referrer_referral) {
        accounts.push(AccountMeta::new(driller_ref, false));
        accounts.push(AccountMeta::new(referral_ref, false));
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ReloadSOL {}.to_bytes(),
    }
}

// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =

/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
pub fn claim_yield(signer: Pubkey, amount: u64, stake_id: u64) -> Instruction {
    let stake_address = stake_pda_with_id(signer, stake_id).0;
    let recipient_address = signer; // SOL recipient (wallet, not token account)
    let pool_address = pool_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(recipient_address, false), // SOL recipient wallet
            AccountMeta::new(stake_address, false),
            AccountMeta::new(pool_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: ClaimYield {
            amount: amount.to_le_bytes(),
        }
        .to_bytes(),
    }
}

pub fn new_var(
    signer: Pubkey,
    provider: Pubkey,
    id: u64,
    commit: [u8; 32],
    samples: u64,
) -> Instruction {
    let board_address = board_pda().0;
    let config_address = config_pda().0;
    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(board_address, false),
            AccountMeta::new(config_address, false),
            AccountMeta::new(provider, false),
            AccountMeta::new(var_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(entropy_rng_api::ID, false),
        ],
        data: NewVar {
            id: id.to_le_bytes(),
            commit: commit,
            samples: samples.to_le_bytes(),
        }
        .to_bytes(),
    }
}

pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
    let config_address = config_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(config_address, false),
            AccountMeta::new_readonly(new_program, false),
        ],
        data: SetSwapProgram {}.to_bytes(),
    }
}

pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
    let board_address = board_pda().0;
    let config_address = config_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(board_address, false),
            AccountMeta::new(config_address, false),
            AccountMeta::new(new_var_address, false),
        ],
        data: SetVarAddress {}.to_bytes(),
    }
}

/// Migrate a Round account to add pool fields.
/// Anyone can call this (signer pays for rent increase).
pub fn migrate(signer: Pubkey, well_id: u64) -> Instruction {
    let well_address = well_pda(well_id).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(well_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: Migrate {}.to_bytes(),
    }
}

/// Migrate a Treasury account (no-op, kept for backwards compatibility).
/// Stakers earn SOL rewards (2% of round winnings), not OIL.
pub fn migrate_treasury(signer: Pubkey) -> Instruction {
    let treasury_address = treasury_pda().0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(treasury_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: Migrate {}.to_bytes(),
    }
}

/// Create a referral account to become a referrer.
pub fn create_referral(signer: Pubkey) -> Instruction {
    let referral_address = referral_pda(signer).0;
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(referral_address, false),
            AccountMeta::new_readonly(system_program::ID, false),
        ],
        data: CreateReferral {}.to_bytes(),
    }
}

/// Claim pending referral rewards (both SOL and OIL).

pub fn claim_referral(signer: Pubkey) -> Instruction {
    let referral_address = referral_pda(signer).0;
    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
    let recipient_oil_address = get_associated_token_address(&signer, &MINT_ADDRESS);
    Instruction {
        program_id: crate::ID,
        accounts: vec![
            AccountMeta::new(signer, true),
            AccountMeta::new(referral_address, false),
            AccountMeta::new(referral_oil_address, false), // Referral account's OIL ATA
            AccountMeta::new(MINT_ADDRESS, false),
            AccountMeta::new(recipient_oil_address, false), // Recipient's OIL ATA
            AccountMeta::new_readonly(system_program::ID, false),
            AccountMeta::new_readonly(spl_token::ID, false),
            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
        ],
        data: ClaimReferral {}.to_bytes(),
    }
}

/// Direct solo bid on an auction well (seize ownership).
/// The bid amount is calculated on-chain as current_price + 1 lamport.
/// User must have enough SOL in their wallet to cover the bid.
/// 
/// Account structure:
/// - Base: signer, square, fund (optional), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program
/// - If previous owner exists (optional): [previous_owner_driller, previous_owner]
pub fn set_bid(
    signer: Pubkey,
    square_id: u64,
    fee_collector: Pubkey,
    pool_account: Option<Pubkey>, // Pool account for current epoch (optional, if pool exists)
    previous_owner_driller: Option<Pubkey>, // Previous owner's driller PDA (if previous owner exists)
    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
) -> Instruction {
    let well_address = well_pda(square_id).0;
    let auction_address = auction_pda().0;
    let treasury_address = treasury_pda().0;
    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
    let staking_pool_address = pool_pda().0;
    let config_address = config_pda().0;
    let mint_authority_address = oil_mint_api::state::authority_pda().0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(well_address, false),
    ];
    
    // Add pool account if provided (for current epoch)
    if let Some(pool_pubkey) = pool_account {
        accounts.push(AccountMeta::new(pool_pubkey, false));
    } else {
        // Add placeholder (account order must be consistent)
        accounts.push(AccountMeta::new_readonly(system_program::ID, false)); // Placeholder, will be ignored
    }
    
    accounts.extend_from_slice(&[
        AccountMeta::new(auction_address, false),
        AccountMeta::new(treasury_address, false),
        AccountMeta::new(treasury_tokens_address, false),
        AccountMeta::new(MINT_ADDRESS, false),
        AccountMeta::new(mint_authority_address, false),
        AccountMeta::new_readonly(oil_mint_api::ID, false),
        AccountMeta::new(staking_pool_address, false),
        AccountMeta::new(fee_collector, false),
        AccountMeta::new(config_address, false),
        AccountMeta::new_readonly(spl_token::ID, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(crate::ID, false), // oil_program
    ]);
    
    // Add previous owner accounts if provided
    if let (Some(driller_pubkey), Some(owner_pubkey)) = (previous_owner_driller, previous_owner) {
        accounts.push(AccountMeta::new(driller_pubkey, false));
        accounts.push(AccountMeta::new(owner_pubkey, false));
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: instruction::SetBid {
            square_id: square_id.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Contribute SOL to an auction pool for a specific well.
/// 
/// Account structure:
/// - Base: signer, bid, square, fund (may need to create), auction, system_program
/// - If pool wins (optional): [treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, previous_owner_driller, previous_owner]
pub fn join_auction_pool(
    signer: Pubkey,
    square_id: u64,
    epoch_id: u64, // Current epoch ID (from Well account)
    amount: u64, // SOL amount in lamports
    auction_pool_account: Pubkey, // Auction pool account for current epoch (may need to create)
    fee_collector: Option<Pubkey>, // Required if pool wins
    previous_owner_driller: Option<Pubkey>, // Required if pool wins and previous owner exists
    previous_owner: Option<Pubkey>, // Required if pool wins and previous owner exists
) -> Instruction {
    let driller_address = driller_pda(signer).0;
    let bid_address = bid_pda(signer, square_id, epoch_id).0;
    let well_address = well_pda(square_id).0;
    let auction_address = auction_pda().0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(driller_address, false), // For tracking pool contributions
        AccountMeta::new(bid_address, false),
        AccountMeta::new(well_address, false),
        AccountMeta::new(auction_pool_account, false), // May need to create
        AccountMeta::new(auction_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(crate::ID, false), // oil_program
    ];
    
    // Add accounts needed if pool wins
    if let Some(fee_collector_pubkey) = fee_collector {
        let treasury_address = treasury_pda().0;
        let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
        let staking_pool_address = pool_pda().0;
        let config_address = config_pda().0;
        let mint_authority_address = oil_mint_api::state::authority_pda().0;
        
        accounts.push(AccountMeta::new(treasury_address, false));
        accounts.push(AccountMeta::new(treasury_tokens_address, false));
        accounts.push(AccountMeta::new(MINT_ADDRESS, false));
        accounts.push(AccountMeta::new(mint_authority_address, false));
        accounts.push(AccountMeta::new_readonly(oil_mint_api::ID, false));
        accounts.push(AccountMeta::new(staking_pool_address, false));
        accounts.push(AccountMeta::new(fee_collector_pubkey, false));
        accounts.push(AccountMeta::new(config_address, false));
        accounts.push(AccountMeta::new_readonly(spl_token::ID, false));
        
        // Add previous owner accounts if provided
        if let (Some(driller_pubkey), Some(owner_pubkey)) = (previous_owner_driller, previous_owner) {
            accounts.push(AccountMeta::new(driller_pubkey, false));
            accounts.push(AccountMeta::new(owner_pubkey, false));
        }
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: JoinAuctionPool {
            square_id: square_id.to_le_bytes(),
            amount: amount.to_le_bytes(),
        }
        .to_bytes(),
    }
}

/// Claim auction-based OIL rewards
/// - OIL rewards: from current ownership and previous ownership (pre-minted)
/// 
/// Account structure:
/// - Base: signer, driller, well accounts (one per well in mask), auction pool accounts (optional, one per well), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, recipient, token_program, associated_token_program, system_program, oil_program
/// - Bid accounts (one per well in mask, required for pool contributors): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
pub fn claim_auction_oil(
    signer: Pubkey,
    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
) -> Instruction {
    let driller_address = driller_pda(signer).0;
    let auction_address = auction_pda().0;
    let treasury_address = treasury_pda().0;
    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
    let mint_authority_address = oil_mint_api::state::authority_pda().0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(driller_address, false),
    ];
    
    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
    for well_opt in well_accounts.iter() {
        if let Some(well_pubkey) = well_opt {
            accounts.push(AccountMeta::new(*well_pubkey, false));
        } else {
            // Use a placeholder account if well is not provided
            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
        }
    }
    
    // Add auction pool accounts if provided (optional, for pool contributors)
    if let Some(auction_pool_pdas) = auction_pool_accounts {
        for auction_pool_pda_opt in auction_pool_pdas.iter() {
            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
            } else {
                // Use a placeholder account if auction pool is not provided
                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
            }
        }
    } else {
        // Add 4 placeholder accounts if auction_pool_accounts is None
        for _ in 0..4 {
            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
        }
    }
    
    accounts.extend_from_slice(&[
        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
        AccountMeta::new(treasury_address, false),
        AccountMeta::new(treasury_tokens_address, false),
        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
        AccountMeta::new_readonly(oil_mint_api::ID, false),
        AccountMeta::new(recipient_address, false),
        AccountMeta::new_readonly(spl_token::ID, false),
        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(crate::ID, false), // oil_program
    ]);
    
    // Add bid accounts if provided (for pool contributors)
    if let Some(bid_pdas) = bid_accounts {
        for bid_pda_opt in bid_pdas.iter() {
            if let Some(bid_pubkey) = bid_pda_opt {
                accounts.push(AccountMeta::new(*bid_pubkey, false));
            }
        }
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ClaimAuctionOIL {
            well_mask,
        }
        .to_bytes(),
    }
}

/// Claim auction-based SOL rewards
/// - SOL rewards: from being outbid and refunds from abandoned pools
/// 
/// Account structure:
/// - Base: signer, driller, well accounts (one per well 0-3, for checking abandoned pools), auction pool accounts (optional, one per well), auction, treasury, system_program, oil_program
/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
pub fn claim_auction_sol(
    signer: Pubkey,
    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
) -> Instruction {
    let driller_address = driller_pda(signer).0;
    let (auction_address, _) = auction_pda();
    let treasury_address = treasury_pda().0;
    
    let mut accounts = vec![
        AccountMeta::new(signer, true),
        AccountMeta::new(driller_address, false),
    ];
    
    // Add well accounts (all 4 wells for checking abandoned pools)
    for well_opt in well_accounts.iter() {
        if let Some(well_pubkey) = well_opt {
            accounts.push(AccountMeta::new(*well_pubkey, false));
        } else {
            // Use a placeholder account if well is not provided
            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
        }
    }
    
    // Add auction pool accounts if provided (optional, for refunds)
    if let Some(auction_pool_pdas) = auction_pool_accounts {
        for auction_pool_pda_opt in auction_pool_pdas.iter() {
            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
            } else {
                // Use a placeholder account if auction pool is not provided
                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
            }
        }
    } else {
        // Add 4 placeholder accounts if auction_pool_accounts is None
        for _ in 0..4 {
            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
        }
    }
    
    accounts.extend_from_slice(&[
        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
        AccountMeta::new(treasury_address, false),
        AccountMeta::new_readonly(system_program::ID, false),
        AccountMeta::new_readonly(crate::ID, false), // oil_program
    ]);
    
    // Add bid accounts if provided (for refunds)
    if let Some(bid_pdas) = bid_accounts {
        for bid_pda_opt in bid_pdas.iter() {
            if let Some(bid_pubkey) = bid_pda_opt {
                accounts.push(AccountMeta::new(*bid_pubkey, false));
            }
        }
    }
    
    Instruction {
        program_id: crate::ID,
        accounts,
        data: ClaimAuctionSOL {
            _reserved: 0,
        }
        .to_bytes(),
    }
}