use solana_sdk::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address_with_program_id;

use crate::constants::*;
use crate::types::PoolAddresses;

pub fn get_config_address(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            CONFIG_SEED,
            &seed.to_le_bytes(),
            mint_x.as_ref(),
            mint_y.as_ref(),
        ],
        program_id,
    )
}

pub fn get_midprice_oracle_address(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            MIDPRICE_ORACLE_SEED,
            &seed.to_le_bytes(),
            mint_x.as_ref(),
            mint_y.as_ref(),
        ],
        program_id,
    )
}

pub fn get_curve_meta_address(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            CURVE_META_SEED,
            &seed.to_le_bytes(),
            mint_x.as_ref(),
            mint_y.as_ref(),
        ],
        program_id,
    )
}

pub fn get_curve_prefabs_address(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            CURVE_PREFABS_SEED,
            &seed.to_le_bytes(),
            mint_x.as_ref(),
            mint_y.as_ref(),
        ],
        program_id,
    )
}

pub fn get_curve_updates_address(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    program_id: &Pubkey,
) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            CURVE_UPDATES_SEED,
            &seed.to_le_bytes(),
            mint_x.as_ref(),
            mint_y.as_ref(),
        ],
        program_id,
    )
}

pub fn get_fee_config_address(program_id: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[FEE_CONFIG_SEED], program_id)
}

pub fn get_pool_fee_config_address(config_pda: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[FEE_CONFIG_SEED, config_pda.as_ref()], program_id)
}

pub fn get_spread_config_address(config_pda: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[SPREAD_CONFIG_SEED, config_pda.as_ref()], program_id)
}

/// Derive all pool PDAs and vault ATAs in one call.
pub fn derive_pool_addresses(
    seed: u64,
    mint_x: &Pubkey,
    mint_y: &Pubkey,
    token_program_x: &Pubkey,
    token_program_y: &Pubkey,
    program_id: &Pubkey,
) -> PoolAddresses {
    let (config, config_bump) = get_config_address(seed, mint_x, mint_y, program_id);
    let (midprice_oracle, _) = get_midprice_oracle_address(seed, mint_x, mint_y, program_id);
    let (curve_meta, _) = get_curve_meta_address(seed, mint_x, mint_y, program_id);
    let (curve_prefabs, _) = get_curve_prefabs_address(seed, mint_x, mint_y, program_id);
    let (curve_updates, _) = get_curve_updates_address(seed, mint_x, mint_y, program_id);

    let vault_x =
        get_associated_token_address_with_program_id(&config, mint_x, token_program_x);
    let vault_y =
        get_associated_token_address_with_program_id(&config, mint_y, token_program_y);

    PoolAddresses {
        config,
        config_bump,
        midprice_oracle,
        curve_meta,
        curve_prefabs,
        curve_updates,
        vault_x,
        vault_y,
    }
}
