#[allow(deprecated)]
use solana_sdk::system_program;
use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
};
use spl_associated_token_account::get_associated_token_address_with_program_id;

use crate::constants::*;
use crate::helpers::derive::*;
use crate::types::*;

/// Build an Initialize instruction (discriminator 0).
///
/// Accounts (14): payer, config, mintX, mintY, vaultX, vaultY,
/// tokenProgramX, tokenProgramY, ataProgram, systemProgram,
/// midpriceOracle, curveMeta, curvePrefabs, curveUpdates.
pub fn build_initialize(
    payer: &Pubkey,
    params: &InitializeParams,
    program_id: &Pubkey,
) -> Instruction {
    let seed = params.seed.unwrap_or(0);
    let token_program_x = params
        .token_program_x
        .unwrap_or(spl_token::id());
    let token_program_y = params
        .token_program_y
        .unwrap_or(spl_token::id());

    let (config_pda, _) = get_config_address(seed, &params.mint_x, &params.mint_y, program_id);
    let vault_x = get_associated_token_address_with_program_id(
        &config_pda,
        &params.mint_x,
        &token_program_x,
    );
    let vault_y = get_associated_token_address_with_program_id(
        &config_pda,
        &params.mint_y,
        &token_program_y,
    );
    let (oracle_pda, _) =
        get_midprice_oracle_address(seed, &params.mint_x, &params.mint_y, program_id);
    let (curve_meta_pda, _) =
        get_curve_meta_address(seed, &params.mint_x, &params.mint_y, program_id);
    let (curve_prefabs_pda, _) =
        get_curve_prefabs_address(seed, &params.mint_x, &params.mint_y, program_id);
    let (curve_updates_pda, _) =
        get_curve_updates_address(seed, &params.mint_x, &params.mint_y, program_id);

    // Build instruction data
    let has_oracle_mode = params.oracle_mode.is_some();
    let has_custom_slots = params.max_prefab_slots.is_some() || params.max_curve_points.is_some();
    let trailing = if has_custom_slots {
        3
    } else if has_oracle_mode {
        1
    } else {
        0
    };
    let data_len = 1 + 8 + 32 + 32 + 32 + 8 + trailing;
    let mut data = Vec::with_capacity(data_len);

    data.push(Discriminator::Initialize as u8);
    data.extend_from_slice(&seed.to_le_bytes());
    data.extend_from_slice(params.mint_x.as_ref());
    data.extend_from_slice(params.mint_y.as_ref());
    data.extend_from_slice(params.authority.as_ref());
    data.extend_from_slice(&params.initial_midprice_q32.to_le_bytes());

    if has_oracle_mode || has_custom_slots {
        data.push(params.oracle_mode.unwrap_or(OracleMode::Authority) as u8);
    }
    if has_custom_slots {
        data.push(params.max_prefab_slots.unwrap_or(DEFAULT_MAX_PREFAB_SLOTS));
        data.push(params.max_curve_points.unwrap_or(DEFAULT_MAX_CURVE_POINTS));
    }

    let keys = vec![
        AccountMeta::new(*payer, true),
        AccountMeta::new(config_pda, false),
        AccountMeta::new_readonly(params.mint_x, false),
        AccountMeta::new_readonly(params.mint_y, false),
        AccountMeta::new(vault_x, false),
        AccountMeta::new(vault_y, false),
        AccountMeta::new_readonly(token_program_x, false),
        AccountMeta::new_readonly(token_program_y, false),
        AccountMeta::new_readonly(spl_associated_token_account::id(), false),
        AccountMeta::new_readonly(system_program::id(), false),
        AccountMeta::new(oracle_pda, false),
        AccountMeta::new(curve_meta_pda, false),
        AccountMeta::new(curve_prefabs_pda, false),
        AccountMeta::new(curve_updates_pda, false),
    ];

    Instruction {
        program_id: *program_id,
        accounts: keys,
        data,
    }
}

/// Build an AllocateCurvePrefabs instruction (discriminator 22).
///
/// Accounts (3): payer, curvePrefabs, systemProgram.
pub fn build_allocate_curve_prefabs(
    payer: &Pubkey,
    params: &AllocateCurvePrefabsParams,
    program_id: &Pubkey,
) -> Instruction {
    let (curve_prefabs_pda, _) =
        get_curve_prefabs_address(params.seed, &params.mint_x, &params.mint_y, program_id);

    let max_slots = params.max_prefab_slots.unwrap_or(DEFAULT_MAX_PREFAB_SLOTS);
    let max_points = params.max_curve_points.unwrap_or(DEFAULT_MAX_CURVE_POINTS);

    let mut data = Vec::with_capacity(108);
    data.push(Discriminator::AllocateCurvePrefabs as u8);
    data.extend_from_slice(&params.seed.to_le_bytes());
    data.extend_from_slice(params.mint_x.as_ref());
    data.extend_from_slice(params.mint_y.as_ref());
    data.push(max_slots);
    data.push(max_points);
    data.extend_from_slice(params.authority.as_ref());

    let keys = vec![
        AccountMeta::new(*payer, true),
        AccountMeta::new(curve_prefabs_pda, false),
        AccountMeta::new_readonly(system_program::id(), false),
    ];

    Instruction {
        program_id: *program_id,
        accounts: keys,
        data,
    }
}
