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

use crate::accounts::*;
use crate::constants::*;
use crate::helpers::derive::*;
use crate::helpers::math::from_q32;
use crate::helpers::sol::{is_native_mint, wrap_sol_instructions, create_wsol_ata_instruction, unwrap_sol_instruction};
use crate::instructions::*;
use crate::types::*;

/// Main SDK struct for interacting with a Hadron pool.
///
/// Usage:
/// ```ignore
/// let pool = Hadron::from_accounts(pool_address, &config_data, &oracle_data, &meta_data, &prefabs_data)?;
/// let midprice = pool.get_midprice();
/// let ix = pool.swap(&user, &params);
/// ```
pub struct Hadron {
    pub program_id: Pubkey,
    pub pool_address: Pubkey,
    pub addresses: PoolAddresses,
    pub config: DecodedConfig,
    pub oracle: DecodedMidpriceOracle,
    pub curve_meta: DecodedCurveMeta,
    pub curve_prefabs_data: Vec<u8>,
}

impl Hadron {
    // ========================================================================
    // Construction
    // ========================================================================

    /// Create a Hadron instance from raw account data bytes (sync, no RPC).
    pub fn from_accounts(
        pool_address: Pubkey,
        config_data: &[u8],
        oracle_data: &[u8],
        curve_meta_data: &[u8],
        curve_prefabs_data: &[u8],
    ) -> Result<Self, HadronSdkError> {
        let program_id = HADRON_PROGRAM_ID;
        let config = decode_config(config_data)?;
        let oracle = decode_midprice_oracle(oracle_data)?;
        let curve_meta = decode_curve_meta(curve_meta_data)?;

        let addresses = derive_pool_addresses(
            config.seed,
            &config.mint_x,
            &config.mint_y,
            &config.token_program_x,
            &config.token_program_y,
            &program_id,
        );

        Ok(Self {
            program_id,
            pool_address,
            addresses,
            config,
            oracle,
            curve_meta,
            curve_prefabs_data: curve_prefabs_data.to_vec(),
        })
    }

    /// Build all instructions needed to initialize a new pool.
    ///
    /// Returns the allocate + initialize instructions, the derived pool address, and the seed.
    pub fn initialize(
        payer: &Pubkey,
        params: &InitializeParams,
        program_id: &Pubkey,
    ) -> (Vec<Instruction>, Pubkey, u64) {
        let seed = params.seed.unwrap_or_else(random_seed);
        let max_prefab_slots = params.max_prefab_slots.unwrap_or(DEFAULT_MAX_PREFAB_SLOTS);
        let max_curve_points = params.max_curve_points.unwrap_or(DEFAULT_MAX_CURVE_POINTS);

        let (pool_address, _) =
            get_config_address(seed, &params.mint_x, &params.mint_y, program_id);

        let allocate_params = AllocateCurvePrefabsParams {
            seed,
            mint_x: params.mint_x,
            mint_y: params.mint_y,
            max_prefab_slots: Some(max_prefab_slots),
            max_curve_points: Some(max_curve_points),
            authority: params.authority,
        };

        let mut instructions = Vec::new();

        let size = curve_prefabs_size(max_prefab_slots, max_curve_points);
        let allocate_calls = if size > 10_240 { 2 } else { 1 };
        for _ in 0..allocate_calls {
            instructions.push(build_allocate_curve_prefabs(
                payer,
                &allocate_params,
                program_id,
            ));
        }

        // Override seed in params for the initialize instruction
        let init_params = InitializeParams {
            seed: Some(seed),
            mint_x: params.mint_x,
            mint_y: params.mint_y,
            authority: params.authority,
            initial_midprice_q32: params.initial_midprice_q32,
            oracle_mode: params.oracle_mode,
            max_prefab_slots: params.max_prefab_slots,
            max_curve_points: params.max_curve_points,
            token_program_x: params.token_program_x,
            token_program_y: params.token_program_y,
        };
        instructions.push(build_initialize(payer, &init_params, program_id));

        (instructions, pool_address, seed)
    }

    // ========================================================================
    // State Queries
    // ========================================================================

    /// Get midprice as a floating-point number.
    pub fn get_midprice(&self) -> f64 {
        from_q32(self.oracle.midprice_q32)
    }

    /// Get spread factor as a floating-point number (e.g. 0.9995 = 5 bps).
    pub fn get_spread_factor(&self) -> f64 {
        from_q32(self.oracle.spread_factor_q32)
    }

    /// Get spread in basis points (e.g. 5 for a 0.9995 factor).
    pub fn get_spread_bps(&self) -> f64 {
        (1.0 - from_q32(self.oracle.spread_factor_q32)) * 10_000.0
    }

    /// Get the currently active curve slot indices.
    pub fn get_active_curve_slots(&self) -> ActiveCurveSlots {
        ActiveCurveSlots {
            price_bid: self.curve_meta.active_price_bid_slot,
            price_ask: self.curve_meta.active_price_ask_slot,
            risk_bid: self.curve_meta.active_risk_bid_slot,
            risk_ask: self.curve_meta.active_risk_ask_slot,
        }
    }

    /// Decode the currently active curves from prefabs data.
    pub fn get_active_curves(&self) -> Result<ActiveCurves, HadronSdkError> {
        decode_active_curves(&self.curve_prefabs_data, &self.curve_meta)
    }

    /// Decode a specific curve slot.
    pub fn get_curve_slot(
        &self,
        curve_type: CurveType,
        slot: u8,
    ) -> Result<CurveSide, HadronSdkError> {
        decode_curve_side(
            &self.curve_prefabs_data,
            curve_type,
            slot,
            self.curve_meta.max_prefab_slots,
            self.curve_meta.max_curve_points,
        )
    }

    // ========================================================================
    // Instruction Builders (convenience wrappers)
    // ========================================================================

    pub fn deposit(&self, user: &Pubkey, params: &DepositParams) -> Instruction {
        build_deposit(
            user,
            &self.pool_address,
            &self.config.mint_x,
            &self.config.mint_y,
            &self.config.token_program_x,
            &self.config.token_program_y,
            params,
            &self.program_id,
        )
    }

    pub fn withdraw(&self, user: &Pubkey, params: &WithdrawParams) -> Instruction {
        build_withdraw(
            user,
            &self.pool_address,
            &self.config.mint_x,
            &self.config.mint_y,
            &self.config.token_program_x,
            &self.config.token_program_y,
            params,
            &self.program_id,
        )
    }

    pub fn swap(&self, user: &Pubkey, params: &SwapParams) -> Instruction {
        build_swap_exact_in(
            user,
            &self.addresses,
            &self.config.mint_x,
            &self.config.mint_y,
            &self.config.token_program_x,
            &self.config.token_program_y,
            params,
            &self.program_id,
            self.config.spread_config_initialized,
            None,
        )
    }

    pub fn set_curve(&self, authority: &Pubkey, params: &SetCurveParams) -> Instruction {
        build_set_curve(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_risk_curve(
        &self,
        authority: &Pubkey,
        params: &SetRiskCurveParams,
    ) -> Instruction {
        build_set_risk_curve(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_risk_curve_absolute(
        &self,
        authority: &Pubkey,
        params: &SetRiskCurveAbsoluteParams,
    ) -> Instruction {
        build_set_risk_curve_absolute(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_curve_both(
        &self,
        authority: &Pubkey,
        params: &SetCurveBothParams,
    ) -> [Instruction; 2] {
        build_set_curve_both(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_risk_curve_both(
        &self,
        authority: &Pubkey,
        params: &SetRiskCurveBothParams,
    ) -> [Instruction; 2] {
        build_set_risk_curve_both(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_risk_curve_absolute_both(
        &self,
        authority: &Pubkey,
        params: &SetRiskCurveAbsoluteBothParams,
    ) -> [Instruction; 2] {
        build_set_risk_curve_absolute_both(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            params,
            &self.program_id,
        )
    }

    pub fn set_quoting_authority(
        &self,
        authority: &Pubkey,
        params: &SetQuotingAuthorityParams,
    ) -> Instruction {
        build_set_quoting_authority(
            authority,
            &self.pool_address,
            &self.addresses.midprice_oracle,
            &self.addresses.curve_meta,
            &self.addresses.curve_updates,
            params,
            &self.program_id,
        )
    }

    pub fn update_midprice(
        &self,
        authority: &Pubkey,
        params: &UpdateMidpriceParams,
    ) -> Instruction {
        build_update_midprice(
            authority,
            &self.addresses.midprice_oracle,
            params,
            &self.program_id,
        )
    }

    pub fn update_base_spread(
        &self,
        authority: &Pubkey,
        params: &UpdateBaseSpreadParams,
    ) -> Instruction {
        build_update_base_spread(
            authority,
            &self.addresses.midprice_oracle,
            params,
            &self.program_id,
        )
    }

    pub fn update_midprice_and_base_spread(
        &self,
        authority: &Pubkey,
        params: &UpdateMidpriceAndBaseSpreadParams,
    ) -> Instruction {
        build_update_midprice_and_base_spread(
            authority,
            &self.addresses.midprice_oracle,
            params,
            &self.program_id,
        )
    }

    pub fn switch_price_curve(
        &self,
        authority: &Pubkey,
        params: &SwitchCurveParams,
    ) -> Instruction {
        build_switch_price_curve(
            authority,
            &self.addresses.curve_meta,
            params,
            &self.program_id,
        )
    }

    pub fn switch_risk_curve(
        &self,
        authority: &Pubkey,
        params: &SwitchCurveParams,
    ) -> Instruction {
        build_switch_risk_curve(
            authority,
            &self.addresses.curve_meta,
            params,
            &self.program_id,
        )
    }

    pub fn submit_curve_updates(
        &self,
        authority: &Pubkey,
        ops: &[CurveUpdateOp],
    ) -> Instruction {
        build_submit_curve_updates(
            authority,
            &self.addresses.curve_updates,
            ops,
            &self.program_id,
        )
    }

    pub fn apply_curve_updates(&self, authority: &Pubkey) -> Instruction {
        build_apply_curve_updates(
            authority,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            &self.addresses.curve_updates,
            &self.program_id,
        )
    }

    pub fn nominate_authority(
        &self,
        authority: &Pubkey,
        params: &NominateAuthorityParams,
    ) -> Instruction {
        build_nominate_authority(authority, &self.pool_address, params, &self.program_id)
    }

    pub fn accept_authority(&self, new_authority: &Pubkey) -> Instruction {
        build_accept_authority(new_authority, &self.pool_address, &self.program_id)
    }

    pub fn set_pool_state(
        &self,
        authority: &Pubkey,
        params: &SetPoolStateParams,
    ) -> Instruction {
        build_set_pool_state(authority, &self.pool_address, params, &self.program_id)
    }

    pub fn update_delta_staleness(
        &self,
        authority: &Pubkey,
        params: &UpdateDeltaStalenessParams,
    ) -> Instruction {
        build_update_delta_staleness(
            authority,
            &self.pool_address,
            params,
            &self.program_id,
        )
    }

    pub fn initialize_spread_config(
        &self,
        payer: &Pubkey,
        authority: &Pubkey,
        params: &InitializeSpreadConfigParams,
    ) -> Instruction {
        build_initialize_spread_config(
            payer,
            authority,
            &self.pool_address,
            params,
            &self.program_id,
        )
    }

    pub fn update_spread_config(
        &self,
        admin: &Pubkey,
        params: &UpdateSpreadConfigParams,
    ) -> Instruction {
        build_update_spread_config(admin, &self.pool_address, params, &self.program_id)
    }

    pub fn close_pool(
        &self,
        authority: &Pubkey,
        authority_ata_x: Option<&Pubkey>,
        authority_ata_y: Option<&Pubkey>,
    ) -> Instruction {
        build_close_pool(
            authority,
            &self.pool_address,
            &self.addresses.midprice_oracle,
            &self.addresses.curve_meta,
            &self.addresses.curve_prefabs,
            &self.addresses.curve_updates,
            &self.addresses.vault_x,
            &self.addresses.vault_y,
            &self.config.token_program_x,
            &self.config.token_program_y,
            self.config.spread_config_initialized,
            authority_ata_x,
            authority_ata_y,
            &self.program_id,
        )
    }

    // ========================================================================
    // SOL Wrapping Convenience Methods
    // ========================================================================

    /// Build deposit instructions with automatic SOL wrapping.
    /// If mint X or Y is native SOL, prepends wrap instructions for that side.
    pub fn deposit_sol(&self, user: &Pubkey, params: &DepositParams) -> Vec<Instruction> {
        let mut ixs = Vec::new();
        if is_native_mint(&self.config.mint_x) && params.amount_x > 0 {
            ixs.extend(wrap_sol_instructions(user, params.amount_x));
        }
        if is_native_mint(&self.config.mint_y) && params.amount_y > 0 {
            ixs.extend(wrap_sol_instructions(user, params.amount_y));
        }
        ixs.push(self.deposit(user, params));
        ixs
    }

    /// Build withdraw instructions with automatic SOL unwrapping.
    /// Creates wSOL ATA (so program can transfer into it), withdraws, then closes ATA.
    pub fn withdraw_sol(&self, user: &Pubkey, params: &WithdrawParams) -> Vec<Instruction> {
        let has_sol = is_native_mint(&self.config.mint_x) || is_native_mint(&self.config.mint_y);
        let mut ixs = Vec::new();
        if has_sol {
            ixs.push(create_wsol_ata_instruction(user));
        }
        ixs.push(self.withdraw(user, params));
        if has_sol {
            ixs.push(unwrap_sol_instruction(user));
        }
        ixs
    }

    /// Build swap instructions with automatic SOL wrapping/unwrapping.
    /// Wraps SOL if the input side is native, unwraps if the output side is native.
    pub fn swap_sol(&self, user: &Pubkey, params: &SwapParams) -> Vec<Instruction> {
        let mut ixs = Vec::new();
        let (input_mint, output_mint) = if params.is_x {
            (&self.config.mint_x, &self.config.mint_y)
        } else {
            (&self.config.mint_y, &self.config.mint_x)
        };

        if is_native_mint(input_mint) {
            ixs.extend(wrap_sol_instructions(user, params.amount_in));
        }
        if is_native_mint(output_mint) {
            ixs.push(create_wsol_ata_instruction(user));
        }
        ixs.push(self.swap(user, params));
        if is_native_mint(output_mint) {
            ixs.push(unwrap_sol_instruction(user));
        }
        ixs
    }
}

#[derive(Debug, Clone)]
pub struct ActiveCurveSlots {
    pub price_bid: u8,
    pub price_ask: u8,
    pub risk_bid: u8,
    pub risk_ask: u8,
}

fn random_seed() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    let duration = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default();
    // Mix nanos with pointer address for uniqueness
    duration.as_nanos() as u64 ^ ((&duration as *const _ as u64).wrapping_mul(6364136223846793005))
}
