use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
};

use crate::constants::Discriminator;
use crate::types::*;

/// Build a SetCurve instruction (discriminator 4).
///
/// Accounts (3): authority(signer), curveMeta(w), curvePrefabs(w).
pub fn build_set_curve(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetCurveParams,
    program_id: &Pubkey,
) -> Instruction {
    let slot = params.slot.unwrap_or(0);
    let x_mode = params.x_mode.unwrap_or(CurveXMode::Native);
    let num_points = params.points.len() as u8;

    let mut data = Vec::with_capacity(6 + params.points.len() * 21);
    data.push(Discriminator::SetCurve as u8);
    data.push(slot);
    data.push(params.side as u8);
    data.push(params.default_interpolation as u8);
    data.push(num_points);
    data.push(x_mode as u8);

    for pt in &params.points {
        data.extend_from_slice(&pt.amount_in.to_le_bytes());
        data.extend_from_slice(&pt.price_factor_q32.to_le_bytes());
        data.push(pt.interpolation.unwrap_or(params.default_interpolation) as u8);
        data.extend_from_slice(&pt.params.unwrap_or([0; 4]));
    }

    let keys = vec![
        AccountMeta::new_readonly(*authority, true),
        AccountMeta::new(*curve_meta_pda, false),
        AccountMeta::new(*curve_prefabs_pda, false),
    ];

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

/// Build a SetRiskCurve instruction (discriminator 8) — percent-based x-axis.
///
/// Accounts (3): authority(signer), curveMeta(w), curvePrefabs(w).
pub fn build_set_risk_curve(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetRiskCurveParams,
    program_id: &Pubkey,
) -> Instruction {
    let slot = params.slot.unwrap_or(0);
    let x_mode = params.x_mode.unwrap_or(CurveXMode::Native);
    let risk_mode = params.risk_mode.unwrap_or(RiskMode::Virtual);
    let num_points = params.points.len() as u8;

    let mut data = Vec::with_capacity(7 + params.points.len() * 21);
    data.push(Discriminator::SetRiskCurve as u8);
    data.push(slot);
    data.push(params.side as u8);
    data.push(params.default_interpolation as u8);
    data.push(num_points);
    data.push(x_mode as u8);
    data.push(risk_mode as u8);

    for pt in &params.points {
        data.extend_from_slice(&pt.pct_base_q32.to_le_bytes());
        data.extend_from_slice(&pt.price_factor_q32.to_le_bytes());
        data.push(pt.interpolation.unwrap_or(params.default_interpolation) as u8);
        data.extend_from_slice(&pt.params.unwrap_or([0; 4]));
    }

    let keys = vec![
        AccountMeta::new_readonly(*authority, true),
        AccountMeta::new(*curve_meta_pda, false),
        AccountMeta::new(*curve_prefabs_pda, false),
    ];

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

/// Build a SetRiskCurveAbsolute instruction (discriminator 8, xMode=Alternate).
///
/// Accounts (3): authority(signer), curveMeta(w), curvePrefabs(w).
pub fn build_set_risk_curve_absolute(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetRiskCurveAbsoluteParams,
    program_id: &Pubkey,
) -> Instruction {
    let slot = params.slot.unwrap_or(0);
    let risk_mode = params.risk_mode.unwrap_or(RiskMode::Virtual);
    let num_points = params.points.len() as u8;

    let mut data = Vec::with_capacity(7 + params.points.len() * 21);
    data.push(Discriminator::SetRiskCurve as u8);
    data.push(slot);
    data.push(params.side as u8);
    data.push(params.default_interpolation as u8);
    data.push(num_points);
    data.push(CurveXMode::Alternate as u8); // always Alternate for absolute
    data.push(risk_mode as u8);

    for pt in &params.points {
        data.extend_from_slice(&pt.vault_balance.to_le_bytes());
        data.extend_from_slice(&pt.price_factor_q32.to_le_bytes());
        data.push(pt.interpolation.unwrap_or(params.default_interpolation) as u8);
        data.extend_from_slice(&pt.params.unwrap_or([0; 4]));
    }

    let keys = vec![
        AccountMeta::new_readonly(*authority, true),
        AccountMeta::new(*curve_meta_pda, false),
        AccountMeta::new(*curve_prefabs_pda, false),
    ];

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

/// Build SetCurve for both bid and ask. Returns [bidIx, askIx].
pub fn build_set_curve_both(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetCurveBothParams,
    program_id: &Pubkey,
) -> [Instruction; 2] {
    let bid_params = SetCurveParams {
        side: Side::Bid,
        default_interpolation: params.bid.default_interpolation,
        points: params.bid.points.iter().map(|p| SetCurvePointInput {
            amount_in: p.amount_in,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.bid.slot,
        x_mode: params.bid.x_mode,
    };
    let ask_params = SetCurveParams {
        side: Side::Ask,
        default_interpolation: params.ask.default_interpolation,
        points: params.ask.points.iter().map(|p| SetCurvePointInput {
            amount_in: p.amount_in,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.ask.slot,
        x_mode: params.ask.x_mode,
    };
    [
        build_set_curve(authority, curve_meta_pda, curve_prefabs_pda, &bid_params, program_id),
        build_set_curve(authority, curve_meta_pda, curve_prefabs_pda, &ask_params, program_id),
    ]
}

/// Build SetRiskCurve for both bid and ask. Returns [bidIx, askIx].
pub fn build_set_risk_curve_both(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetRiskCurveBothParams,
    program_id: &Pubkey,
) -> [Instruction; 2] {
    let bid_params = SetRiskCurveParams {
        side: Side::Bid,
        default_interpolation: params.bid.default_interpolation,
        points: params.bid.points.iter().map(|p| SetRiskCurvePointInput {
            pct_base_q32: p.pct_base_q32,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.bid.slot,
        x_mode: params.bid.x_mode,
        risk_mode: params.bid.risk_mode,
    };
    let ask_params = SetRiskCurveParams {
        side: Side::Ask,
        default_interpolation: params.ask.default_interpolation,
        points: params.ask.points.iter().map(|p| SetRiskCurvePointInput {
            pct_base_q32: p.pct_base_q32,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.ask.slot,
        x_mode: params.ask.x_mode,
        risk_mode: params.ask.risk_mode,
    };
    [
        build_set_risk_curve(authority, curve_meta_pda, curve_prefabs_pda, &bid_params, program_id),
        build_set_risk_curve(authority, curve_meta_pda, curve_prefabs_pda, &ask_params, program_id),
    ]
}

/// Build SetRiskCurveAbsolute for both bid and ask. Returns [bidIx, askIx].
pub fn build_set_risk_curve_absolute_both(
    authority: &Pubkey,
    curve_meta_pda: &Pubkey,
    curve_prefabs_pda: &Pubkey,
    params: &SetRiskCurveAbsoluteBothParams,
    program_id: &Pubkey,
) -> [Instruction; 2] {
    let bid_params = SetRiskCurveAbsoluteParams {
        side: Side::Bid,
        default_interpolation: params.bid.default_interpolation,
        points: params.bid.points.iter().map(|p| SetRiskCurveAbsolutePointInput {
            vault_balance: p.vault_balance,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.bid.slot,
        risk_mode: params.bid.risk_mode,
    };
    let ask_params = SetRiskCurveAbsoluteParams {
        side: Side::Ask,
        default_interpolation: params.ask.default_interpolation,
        points: params.ask.points.iter().map(|p| SetRiskCurveAbsolutePointInput {
            vault_balance: p.vault_balance,
            price_factor_q32: p.price_factor_q32,
            interpolation: p.interpolation,
            params: p.params,
        }).collect(),
        slot: params.ask.slot,
        risk_mode: params.ask.risk_mode,
    };
    [
        build_set_risk_curve_absolute(authority, curve_meta_pda, curve_prefabs_pda, &bid_params, program_id),
        build_set_risk_curve_absolute(authority, curve_meta_pda, curve_prefabs_pda, &ask_params, program_id),
    ]
}
