use cosmwasm_std::{
    entry_point, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo,
    Response, StdResult, Uint128, Uint256, WasmMsg,
};
use cw2::{get_contract_version, set_contract_version};

use crate::emission::emission_rate_per_second;
use crate::error::ContractError;
use crate::msg::{
    ConfigResponse, EmissionInfoResponse, ExecuteMsg, InstantiateMsg, MinerStatsResponse, QueryMsg,
    RewardCalculationResponse, StatsResponse, TestingOverrides, WindowStatusResponse,
};
use crate::state::{
    FeeBucket, FeeHistory, MineConfig, MinerStats, PidState, SlidingWindow, Stats, WindowEntry,
    CONFIG, FEE_HISTORY, MINER_STATS, PID_STATE, PROOF_PRUNE_CURSOR, SLIDING_WINDOW, STATS,
    USED_PROOF_HASHES,
};

const CONTRACT_NAME: &str = "crates.io:litium-mine";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

const DEFAULT_WINDOW_SIZE: u32 = 5000;
const DEFAULT_PID_INTERVAL: u64 = 100;
const DEFAULT_GAS_COST_UBOOT: u128 = 250_000;

/// Absolute floor for proof difficulty โ€” cheap early reject (spec ยง8.4).
const BASE_DIFFICULTY_TARGET: u32 = 8;
/// Cap for dynamic min_profitable_difficulty to prevent nonsensical values.
const MAX_MIN_PROFITABLE_DIFFICULTY: u32 = 32;
const DEFAULT_FEE_BUCKET_DURATION: u64 = 600;
const DEFAULT_FEE_NUM_BUCKETS: u32 = 36;

/// Referral share from each accepted proof reward.
const REFERRAL_SHARE_NUM: u128 = 10;
const REFERRAL_SHARE_DEN: u128 = 100;

/// PID alpha/beta are stored as micros (per-million).
/// alpha range: [300_000, 700_000] โ†’ [0.3, 0.7]
/// beta range:  [0, 900_000]       โ†’ [0.0, 0.9]
const ALPHA_MIN: u64 = 300_000;
const ALPHA_MAX: u64 = 700_000;
const BETA_MIN: u64 = 0;
const BETA_MAX: u64 = 900_000;
const MICROS: u64 = 1_000_000;

/// PID gains (moderate PD mode from spec) โ€” scaled by 1e6 for integer math.
const KP_A: i64 = 4_000; // 0.004 * 1e6
const KD_A: i64 = 8_000; // 0.008 * 1e6
const KP_B: i64 = 15_000; // 0.015 * 1e6
const KD_B: i64 = 30_000; // 0.03 * 1e6
/// Warmup P-only gains (spec ยง6 conservative mode) โ€” D gains zeroed.
const KP_A_WARMUP: i64 = 5_000; // 0.005 * 1e6
const KP_B_WARMUP: i64 = 20_000; // 0.02 * 1e6
/// EMA smoothing factor lambda=0.3, stored as 300_000 (per-million).
const EMA_LAMBDA: i64 = 300_000;

/// Compute dynamic minimum profitable difficulty (spec ยง8.4).
///
/// d_breakeven = gas_cost / base_rate
/// min_profitable = 2 * d_breakeven
/// result = max(BASE_DIFFICULTY_TARGET, min(min_profitable, MAX_CAP))
fn compute_min_profitable_difficulty(gas_cost: Uint128, base_rate: Uint128) -> u32 {
    if gas_cost.is_zero() || base_rate.is_zero() {
        return BASE_DIFFICULTY_TARGET;
    }
    let min_profitable = (gas_cost
        .checked_mul(Uint128::from(2u128))
        .unwrap_or(Uint128::MAX))
    .checked_div(base_rate)
    .unwrap_or(Uint128::zero());
    let capped = min_profitable
        .u128()
        .min(MAX_MIN_PROFITABLE_DIFFICULTY as u128) as u32;
    BASE_DIFFICULTY_TARGET.max(capped)
}
/// Scale factor for PID error signals (1e9).
const ERR_SCALE: i64 = 1_000_000_000;

// ============================================================
// Cross-contract message types
// ============================================================

#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumCoreExecuteMsg {
    Mint { to: String, amount: Uint128 },
}

#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumReferExecuteMsg {
    BindReferrer { miner: String, referrer: String },
    AccrueReward { miner: String, amount: Uint128 },
}

#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumStakeExecuteMsg {
    AccrueReward { amount: Uint128 },
}

#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumStakeQueryMsg {
    TotalStaked {},
    TotalPendingRewards {},
}

#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumCoreQueryMsg {
    TotalMinted {},
    BurnStats {},
}

#[derive(serde::Serialize, serde::Deserialize)]
struct TotalStakedResponse {
    total_staked: Uint128,
}

#[derive(serde::Serialize, serde::Deserialize)]
struct TotalPendingRewardsResponse {
    total_pending_rewards: Uint128,
}

#[derive(serde::Serialize, serde::Deserialize)]
struct TotalMintedResponse {
    total_minted: Uint128,
    #[allow(dead_code)]
    supply_cap: Uint128,
}

#[derive(serde::Serialize, serde::Deserialize)]
struct BurnStatsResponse {
    total_burned: Uint128,
}

// ============================================================
// Instantiate
// ============================================================

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    let admin = msg
        .admin
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?
        .unwrap_or(info.sender.clone());

    let window_size = msg.window_size.unwrap_or(DEFAULT_WINDOW_SIZE);
    let pid_interval = msg.pid_interval.unwrap_or(DEFAULT_PID_INTERVAL);
    let gas_cost = msg
        .estimated_gas_cost_uboot
        .unwrap_or(Uint128::from(DEFAULT_GAS_COST_UBOOT));

    let fee_bucket_duration = msg
        .fee_bucket_duration
        .unwrap_or(DEFAULT_FEE_BUCKET_DURATION);
    let fee_num_buckets = msg.fee_num_buckets.unwrap_or(DEFAULT_FEE_NUM_BUCKETS);

    let config = MineConfig {
        max_proof_age: msg.max_proof_age,
        estimated_gas_cost_uboot: gas_cost,
        core_contract: deps.api.addr_validate(&msg.core_contract)?,
        stake_contract: deps.api.addr_validate(&msg.stake_contract)?,
        refer_contract: deps.api.addr_validate(&msg.refer_contract)?,
        token_contract: msg.token_contract,
        admin,
        paused: false,
        window_size,
        pid_interval,
        genesis_time: msg.genesis_time.unwrap_or(1_772_619_159),
        warmup_base_rate: msg.warmup_base_rate,
        fee_bucket_duration,
        fee_num_buckets,
    };
    CONFIG.save(deps.storage, &config)?;

    // Initialize empty fee history
    let fee_history = FeeHistory {
        buckets: (0..fee_num_buckets)
            .map(|_| FeeBucket {
                epoch: 0,
                amount: Uint128::zero(),
            })
            .collect(),
        bucket_duration: fee_bucket_duration,
    };
    FEE_HISTORY.save(deps.storage, &fee_history)?;

    // Initialize empty sliding window
    let window = SlidingWindow {
        entries: Vec::new(),
        head: 0,
        count: 0,
        total_d: 0,
        t_first: 0,
        t_last: 0,
    };
    SLIDING_WINDOW.save(deps.storage, &window)?;

    // Initialize PID state: alpha=0.5 (500_000 micros), beta=0.0
    let pid = PidState {
        alpha: 500_000,
        beta: 0,
        e_eff_prev: 0,
        e_cov_prev: 0,
        de_eff: 0,
        de_cov: 0,
        cached_staking_share: 0,
    };
    PID_STATE.save(deps.storage, &pid)?;

    let stats = Stats {
        total_proofs: 0,
        total_rewards: Uint128::zero(),
        unique_miners: 0,
        total_difficulty_bits: 0,
    };
    STATS.save(deps.storage, &stats)?;

    Ok(Response::new()
        .add_attribute("action", "instantiate")
        .add_attribute("window_size", window_size.to_string())
        .add_attribute("admin", config.admin.to_string()))
}

// ============================================================
// Execute
// ============================================================

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::SubmitProof {
            hash,
            nonce,
            miner_address,
            challenge,
            difficulty,
            timestamp,
            referrer,
        } => execute_submit_proof(
            deps,
            env,
            info,
            hash,
            nonce,
            miner_address,
            challenge,
            difficulty,
            timestamp,
            referrer,
        ),
        ExecuteMsg::AccrueFees { amount } => execute_accrue_fees(deps, env, info, amount),
        ExecuteMsg::UpdateConfig {
            max_proof_age,
            admin,
            estimated_gas_cost_uboot,
            core_contract,
            stake_contract,
            refer_contract,
            warmup_base_rate,
            pid_interval,
            genesis_time,
        } => execute_update_config(
            deps,
            info,
            max_proof_age,
            admin,
            estimated_gas_cost_uboot,
            core_contract,
            stake_contract,
            refer_contract,
            warmup_base_rate,
            pid_interval,
            genesis_time,
        ),
        ExecuteMsg::ApplyTestingOverrides { overrides } => {
            execute_apply_testing_overrides(deps, info, overrides)
        }
        ExecuteMsg::ResetState { genesis_time } => {
            execute_reset_state(deps, env, info, genesis_time)
        }
        ExecuteMsg::Pause {} => execute_pause(deps, info),
        ExecuteMsg::Unpause {} => execute_unpause(deps, info),
    }
}

// ============================================================
// Submit Proof (core on_proof flow)
// ============================================================

#[allow(clippy::too_many_arguments)]
fn execute_submit_proof(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    hash_hex: String,
    nonce: u64,
    miner_address: String,
    challenge_hex: String,
    claimed_difficulty: u32,
    timestamp: u64,
    referrer: Option<String>,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }

    // 1. Cheap early reject: absolute difficulty floor (spec ยง8.4)
    if claimed_difficulty < BASE_DIFFICULTY_TARGET {
        return Err(ContractError::BelowProfitableThreshold {
            got: claimed_difficulty,
            min: BASE_DIFFICULTY_TARGET,
        });
    }

    let miner = deps.api.addr_validate(&miner_address)?;

    // Sender must be the miner (prevents referrer hijacking via front-running)
    if info.sender != miner {
        return Err(ContractError::Unauthorized {});
    }

    // 2. Verify proof (hash, timestamp, difficulty)
    let (valid, _actual_difficulty_bits) = verify_proof(
        nonce,
        &challenge_hex,
        &hash_hex,
        claimed_difficulty,
        config.max_proof_age,
        timestamp,
        env.block.time.seconds(),
    )?;
    if !valid {
        return Err(ContractError::HashMismatch {});
    }

    // 3. Anti-replay: check proof hash not already used
    let hash_bytes = hex::decode(&hash_hex).map_err(|_| ContractError::InvalidHash {})?;
    if USED_PROOF_HASHES.has(deps.storage, &hash_bytes) {
        return Err(ContractError::DuplicateProofHash {});
    }
    USED_PROOF_HASHES.save(deps.storage, &hash_bytes, &timestamp)?;

    // Amortized pruning: remove up to 10 stale proof hashes per submission
    prune_old_proof_hashes(
        deps.storage,
        env.block.time.seconds(),
        config.max_proof_age,
        10,
    );

    // 4. Check referrer constraints
    let mut miner_stats = MINER_STATS
        .may_load(deps.storage, &miner)?
        .unwrap_or(MinerStats {
            proofs_submitted: 0,
            total_rewards: Uint128::zero(),
            last_proof_time: 0,
        });
    let is_first_proof = miner_stats.proofs_submitted == 0;
    if !is_first_proof && referrer.is_some() {
        return Err(ContractError::ReferrerLockedAfterFirstProof {});
    }

    // 5. Compute reward using sliding window
    // Use claimed_difficulty (not actual_difficulty_bits) per spec:
    // reward(d) = base_rate * d, where d is client-chosen difficulty.
    // actual_difficulty_bits may be higher due to hash luck, but reward is
    // based on what was committed to, ensuring fair pricing.
    let pid = PID_STATE.load(deps.storage)?;
    let window = SLIDING_WINDOW.load(deps.storage)?;
    let fee_history = FEE_HISTORY.load(deps.storage)?;
    let now = env.block.time.seconds();
    let d = claimed_difficulty;

    let (total_reward, base_rate_used) =
        compute_proof_reward(&config, &window, &pid, &fee_history, now, d)?;

    // 5b. Economic reject: dynamic min profitable difficulty (spec ยง8.4)
    let min_profitable_d =
        compute_min_profitable_difficulty(config.estimated_gas_cost_uboot, base_rate_used);
    if claimed_difficulty < min_profitable_d {
        return Err(ContractError::BelowProfitableThreshold {
            got: claimed_difficulty,
            min: min_profitable_d,
        });
    }

    // 6. Deduct gas cost from gross reward (gas is not minted โ€” equivalent to fee payment)
    let gas_deduction = std::cmp::min(config.estimated_gas_cost_uboot, total_reward);
    let net_reward = total_reward.saturating_sub(gas_deduction);

    // 7. Split net reward per spec ยง5: 10% referral, 90% split between stakers/miners
    let referral_reward = net_reward.multiply_ratio(REFERRAL_SHARE_NUM, REFERRAL_SHARE_DEN);
    let post_referral = net_reward.saturating_sub(referral_reward);
    let staking_share = compute_staking_share(&deps.as_ref(), &config, &pid);
    let staking_reward = post_referral.multiply_ratio(staking_share as u128, MICROS as u128);
    let miner_reward = post_referral.saturating_sub(staking_reward);

    // 8. Update sliding window (ring buffer push) โ€” use claimed difficulty
    let mut window = window;
    push_to_window(&mut window, config.window_size, claimed_difficulty, now);
    SLIDING_WINDOW.save(deps.storage, &window)?;

    // 9. Update stats
    let mut stats = STATS.load(deps.storage)?;
    stats.total_proofs += 1;
    stats.total_rewards += total_reward;
    stats.total_difficulty_bits += claimed_difficulty as u64;
    if is_first_proof {
        stats.unique_miners += 1;
    }
    STATS.save(deps.storage, &stats)?;

    miner_stats.proofs_submitted += 1;
    miner_stats.total_rewards += miner_reward;
    miner_stats.last_proof_time = now;
    MINER_STATS.save(deps.storage, &miner, &miner_stats)?;

    // 10. PID update every K proofs
    if config.pid_interval > 0 && window.count % config.pid_interval == 0 && window.count > 0 {
        let mut pid = PID_STATE.load(deps.storage)?;
        let is_warmup = window.entries.len() < config.window_size as usize;
        pid_update(
            &deps.as_ref(),
            &config,
            &window,
            &fee_history,
            now,
            &mut pid,
            is_warmup,
        )?;
        PID_STATE.save(deps.storage, &pid)?;
    }

    // 11. Build response with cross-contract messages
    let mut resp = Response::new()
        .add_attribute("action", "submit_proof")
        .add_attribute("miner", miner.to_string())
        .add_attribute("difficulty", d.to_string())
        .add_attribute("gross_reward", total_reward.to_string())
        .add_attribute("gas_deduction", gas_deduction.to_string())
        .add_attribute("net_reward", net_reward.to_string())
        .add_attribute("miner_reward", miner_reward.to_string())
        .add_attribute("staking_reward", staking_reward.to_string())
        .add_attribute("referral_reward", referral_reward.to_string())
        .add_attribute("base_rate", base_rate_used.to_string())
        .add_attribute("min_profitable_difficulty", min_profitable_d.to_string());

    // Mint mining reward via litium-core
    if !miner_reward.is_zero() {
        resp = resp.add_message(build_core_mint_msg(
            &config,
            miner.to_string(),
            miner_reward,
        )?);
    }

    // Bind referrer + accrue referral reward via litium-refer
    if let Some(ref referrer_addr) = referrer {
        let bind_msg = CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: config.refer_contract.to_string(),
            msg: cosmwasm_std::to_json_binary(&LitiumReferExecuteMsg::BindReferrer {
                miner: miner.to_string(),
                referrer: referrer_addr.clone(),
            })?,
            funds: vec![],
        });
        resp = resp.add_message(bind_msg);
    }

    if !referral_reward.is_zero() {
        let accrue_ref_msg = CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: config.refer_contract.to_string(),
            msg: cosmwasm_std::to_json_binary(&LitiumReferExecuteMsg::AccrueReward {
                miner: miner.to_string(),
                amount: referral_reward,
            })?,
            funds: vec![],
        });
        resp = resp.add_message(accrue_ref_msg);
    }

    // Accrue staking reward via litium-stake
    if !staking_reward.is_zero() {
        let accrue_stake_msg = CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: config.stake_contract.to_string(),
            msg: cosmwasm_std::to_json_binary(&LitiumStakeExecuteMsg::AccrueReward {
                amount: staking_reward,
            })?,
            funds: vec![],
        });
        resp = resp.add_message(accrue_stake_msg);
    }

    Ok(resp)
}

// ============================================================
// Reward Computation
// ============================================================

/// Compute reward for a proof with `d` difficulty bits.
/// Returns (total_reward, base_rate_used).
pub fn compute_proof_reward(
    config: &MineConfig,
    window: &SlidingWindow,
    pid: &PidState,
    fee_history: &FeeHistory,
    now: u64,
    d: u32,
) -> Result<(Uint128, Uint128), ContractError> {
    let window_entries = window.entries.len() as u32;

    // During warmup (window not full), use fixed base_rate
    if window_entries < config.window_size {
        let base_rate = config.warmup_base_rate;
        let reward = base_rate.checked_mul(Uint128::from(d as u128))?;
        return Ok((reward.max(Uint128::one()), base_rate));
    }

    // Normal mode: compute from sliding window
    let time_span = window.t_last.saturating_sub(window.t_first);
    if time_span == 0 {
        // All proofs at same timestamp โ€” use warmup rate
        let base_rate = config.warmup_base_rate;
        let reward = base_rate.checked_mul(Uint128::from(d as u128))?;
        return Ok((reward.max(Uint128::one()), base_rate));
    }

    let d_rate_num = window.total_d; // sum of difficulty bits in window
    let d_rate_den = time_span; // seconds

    // E_target = emission_rate_per_second(genesis_time, now)
    let e_target = emission_rate_per_second(config.genesis_time, now);

    // Windowed fee rate: F = recent_fees(window.time_span) / time_span
    let windowed_fees = fee_history_windowed_sum(fee_history, window.t_first, window.t_last);
    let fee_rate_den = Uint128::from(time_span);

    // G = E_target + F/time_span * (1 - beta)
    let beta_complement = MICROS - pid.beta.min(MICROS); // (1 - beta) in micros
    let fee_contribution = if fee_rate_den.is_zero() {
        Uint128::zero()
    } else {
        windowed_fees
            .multiply_ratio(beta_complement, MICROS)
            .checked_div(fee_rate_den)
            .unwrap_or(Uint128::zero())
    };
    let gross_rate = e_target.checked_add(fee_contribution)?; // G per second

    // base_rate = G / D_rate  (staking/referral split happens in execute_submit_proof)
    //           = gross_rate / (d_rate_num / d_rate_den)
    //           = (gross_rate * d_rate_den) / d_rate_num
    let base_rate = if d_rate_num == 0 {
        config.warmup_base_rate
    } else {
        let numerator = Uint256::from(gross_rate).checked_mul(Uint256::from(d_rate_den))?;
        let denominator = Uint256::from(d_rate_num);
        if denominator.is_zero() {
            Uint256::from(config.warmup_base_rate)
        } else {
            numerator.checked_div(denominator)?
        }
        .try_into()
        .unwrap_or(config.warmup_base_rate)
    };

    // reward = base_rate * d
    let reward = base_rate.checked_mul(Uint128::from(d as u128))?;
    Ok((reward.max(Uint128::one()), base_rate))
}

/// Compute staking share S^alpha as micros [0, MICROS].
/// Queries litium-stake and litium-core for live staked/supply values.
fn compute_staking_share(deps: &Deps, config: &MineConfig, pid: &PidState) -> u64 {
    let s_ratio = query_staking_ratio(deps, config).unwrap_or(0.0);
    let alpha = pid.alpha as f64 / MICROS as f64;
    let share = s_ratio.powf(alpha);
    (share * MICROS as f64).min(MICROS as f64) as u64
}

/// Query staking ratio S = staked / effective_supply.
///
/// Effective supply includes accrued-but-unminted staking rewards to prevent
/// the "sawtooth" effect where claiming staking rewards mass-mints tokens and
/// suddenly changes the S-ratio.
fn query_staking_ratio(deps: &Deps, config: &MineConfig) -> StdResult<f64> {
    let staked: TotalStakedResponse = deps.querier.query_wasm_smart(
        config.stake_contract.to_string(),
        &LitiumStakeQueryMsg::TotalStaked {},
    )?;
    let minted: TotalMintedResponse = deps.querier.query_wasm_smart(
        config.core_contract.to_string(),
        &LitiumCoreQueryMsg::TotalMinted {},
    )?;
    let burned: BurnStatsResponse = deps.querier.query_wasm_smart(
        config.core_contract.to_string(),
        &LitiumCoreQueryMsg::BurnStats {},
    )?;
    let pending: TotalPendingRewardsResponse = deps.querier.query_wasm_smart(
        config.stake_contract.to_string(),
        &LitiumStakeQueryMsg::TotalPendingRewards {},
    )?;

    // effective supply = minted - burned + pending staking rewards (unminted but accrued)
    let circulating =
        minted.total_minted.saturating_sub(burned.total_burned) + pending.total_pending_rewards;
    if circulating.is_zero() {
        return Ok(0.0);
    }

    let ratio = staked.total_staked.u128() as f64 / circulating.u128() as f64;
    Ok(ratio.min(1.0))
}

// ============================================================
// Proof Hash Pruning
// ============================================================

/// Amortized pruning of stale proof hashes. Scans up to `limit` entries from
/// a stored cursor, deleting those with timestamps older than `now - max_age`.
fn prune_old_proof_hashes(
    storage: &mut dyn cosmwasm_std::Storage,
    now: u64,
    max_age: u64,
    limit: usize,
) {
    use cosmwasm_std::Order;
    let cutoff = now.saturating_sub(max_age);
    let cursor = PROOF_PRUNE_CURSOR.may_load(storage).ok().flatten();
    let start = cursor.as_deref().map(cw_storage_plus::Bound::exclusive);
    let entries: Vec<(Vec<u8>, u64)> = USED_PROOF_HASHES
        .range(storage, start, None, Order::Ascending)
        .take(limit)
        .filter_map(|r| r.ok())
        .collect();

    let mut last_key = None;
    for (key, ts) in &entries {
        if *ts < cutoff {
            USED_PROOF_HASHES.remove(storage, key);
        }
        last_key = Some(key.clone());
    }

    if entries.len() < limit {
        // Wrapped around โ€” reset cursor so next pass starts from beginning
        PROOF_PRUNE_CURSOR.remove(storage);
    } else if let Some(key) = last_key {
        let _ = PROOF_PRUNE_CURSOR.save(storage, &key);
    }
}

// ============================================================
// Sliding Window Operations
// ============================================================

/// Push a new entry into the ring buffer.
pub fn push_to_window(window: &mut SlidingWindow, max_size: u32, difficulty: u32, timestamp: u64) {
    let max_size = max_size as usize;

    if window.entries.len() < max_size {
        // Window not full yet โ€” just append
        window.entries.push(WindowEntry {
            difficulty,
            timestamp,
        });
    } else {
        // Ring buffer: overwrite oldest entry
        let head = window.head as usize;
        let old = &window.entries[head];
        window.total_d = window.total_d.saturating_sub(old.difficulty as u64);
        window.entries[head] = WindowEntry {
            difficulty,
            timestamp,
        };
        window.head = ((head + 1) % max_size) as u32;

        // Recompute t_first from the entry at the new head position
        let new_oldest = window.head as usize;
        window.t_first = window.entries[new_oldest].timestamp;
    }

    window.total_d += difficulty as u64;
    window.t_last = timestamp;
    window.count += 1;

    // Set t_first on first entry
    if window.count == 1 || window.entries.len() == 1 {
        window.t_first = timestamp;
    }
}

// ============================================================
// PID Controller
// ============================================================

fn pid_update(
    deps: &Deps,
    config: &MineConfig,
    window: &SlidingWindow,
    fee_history: &FeeHistory,
    now: u64,
    pid: &mut PidState,
    is_warmup: bool,
) -> StdResult<()> {
    let s_ratio = query_staking_ratio(deps, config).unwrap_or(0.0);
    let alpha_f = pid.alpha as f64 / MICROS as f64;

    let time_span = window.t_last.saturating_sub(window.t_first).max(1) as f64;
    let d_rate = window.total_d as f64 / time_span;

    let e = emission_rate_per_second(config.genesis_time, now).u128() as f64;
    let windowed_fees = fee_history_windowed_sum(fee_history, window.t_first, window.t_last);
    let fee_rate = windowed_fees.u128() as f64 / time_span;

    let beta_f = pid.beta as f64 / MICROS as f64;
    let g = e + fee_rate * (1.0 - beta_f);

    let s_alpha = s_ratio.powf(alpha_f);
    let r_pow = g * (1.0 - s_alpha);
    let r_pos = g * s_alpha;

    // Efficiency errors
    let eta_pow = if r_pow > 0.0 { d_rate / r_pow } else { 0.0 };
    let eta_pos = if r_pos > 0.0 {
        let minted: TotalMintedResponse = deps
            .querier
            .query_wasm_smart(
                config.core_contract.to_string(),
                &LitiumCoreQueryMsg::TotalMinted {},
            )
            .unwrap_or(TotalMintedResponse {
                total_minted: Uint128::zero(),
                supply_cap: Uint128::zero(),
            });
        let burned: BurnStatsResponse = deps
            .querier
            .query_wasm_smart(
                config.core_contract.to_string(),
                &LitiumCoreQueryMsg::BurnStats {},
            )
            .unwrap_or(BurnStatsResponse {
                total_burned: Uint128::zero(),
            });
        let pending: TotalPendingRewardsResponse = deps
            .querier
            .query_wasm_smart(
                config.stake_contract.to_string(),
                &LitiumStakeQueryMsg::TotalPendingRewards {},
            )
            .unwrap_or(TotalPendingRewardsResponse {
                total_pending_rewards: Uint128::zero(),
            });
        let circulating = (minted.total_minted.saturating_sub(burned.total_burned)
            + pending.total_pending_rewards)
            .u128() as f64;
        (s_ratio * circulating) / r_pos
    } else {
        0.0
    };
    let e_eff = ((eta_pow - eta_pos) * ERR_SCALE as f64) as i64;
    let e_cov = if e > 0.0 {
        ((fee_rate / e - 1.0) * ERR_SCALE as f64) as i64
    } else {
        0
    };

    // EMA derivatives
    let de_eff_raw = e_eff.saturating_sub(pid.e_eff_prev);
    let de_eff =
        (EMA_LAMBDA * de_eff_raw / 1_000_000) + ((1_000_000 - EMA_LAMBDA) * pid.de_eff / 1_000_000);
    let de_cov_raw = e_cov.saturating_sub(pid.e_cov_prev);
    let de_cov =
        (EMA_LAMBDA * de_cov_raw / 1_000_000) + ((1_000_000 - EMA_LAMBDA) * pid.de_cov / 1_000_000);

    // Select gains: P-only during warmup (spec ยง6), moderate PD otherwise
    let (kp_a, kd_a, kp_b, kd_b) = if is_warmup {
        (KP_A_WARMUP, 0i64, KP_B_WARMUP, 0i64)
    } else {
        (KP_A, KD_A, KP_B, KD_B)
    };

    // Update alpha: alpha += Kp_a * e_eff + Kd_a * de_eff (all in micros)
    let alpha_delta = (kp_a * e_eff / ERR_SCALE) + (kd_a * de_eff / ERR_SCALE);
    let new_alpha = (pid.alpha as i64 + alpha_delta).clamp(ALPHA_MIN as i64, ALPHA_MAX as i64);
    pid.alpha = new_alpha as u64;

    // Update beta: beta += Kp_b * e_cov + Kd_b * de_cov
    let beta_delta = (kp_b * e_cov / ERR_SCALE) + (kd_b * de_cov / ERR_SCALE);
    let new_beta = (pid.beta as i64 + beta_delta).clamp(BETA_MIN as i64, BETA_MAX as i64);
    pid.beta = new_beta as u64;

    pid.e_eff_prev = e_eff;
    pid.e_cov_prev = e_cov;
    pid.de_eff = de_eff;
    pid.de_cov = de_cov;

    // Cache S^alpha for use in base_rate computation between PID updates
    let new_alpha_f = pid.alpha as f64 / MICROS as f64;
    let new_s_alpha = s_ratio.powf(new_alpha_f);
    pid.cached_staking_share = (new_s_alpha * MICROS as f64).min(MICROS as f64) as u64;

    Ok(())
}

// ============================================================
// AccrueFees (from litium-core)
// ============================================================

fn execute_accrue_fees(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.core_contract {
        return Err(ContractError::UnauthorizedFeeAccrual {});
    }

    // Store RAW fee amount into time-bucketed history for PID controller
    let now = env.block.time.seconds();
    let mut history = FEE_HISTORY.load(deps.storage)?;
    fee_history_accrue(&mut history, now, amount);
    FEE_HISTORY.save(deps.storage, &history)?;

    // Fee is already burned in litium-core (spec ยง4). We only track it here.
    Ok(Response::new()
        .add_attribute("action", "accrue_fees")
        .add_attribute("amount", amount.to_string()))
}

// ============================================================
// Fee History Helpers
// ============================================================

/// Accrue a raw fee amount into the appropriate time bucket.
fn fee_history_accrue(history: &mut FeeHistory, now: u64, raw_amount: Uint128) {
    if history.buckets.is_empty() || history.bucket_duration == 0 {
        return;
    }
    let current_epoch = now / history.bucket_duration;
    let num_buckets = history.buckets.len();
    let slot = (current_epoch as usize) % num_buckets;
    if history.buckets[slot].epoch != current_epoch {
        // Stale bucket โ€” reset
        history.buckets[slot] = FeeBucket {
            epoch: current_epoch,
            amount: raw_amount,
        };
    } else {
        history.buckets[slot].amount += raw_amount;
    }
}

/// Sum raw fees whose bucket epoch falls within [t_min/duration, t_max/duration].
pub fn fee_history_windowed_sum(history: &FeeHistory, t_min: u64, t_max: u64) -> Uint128 {
    if history.buckets.is_empty() || history.bucket_duration == 0 {
        return Uint128::zero();
    }
    let epoch_min = t_min / history.bucket_duration;
    let epoch_max = t_max / history.bucket_duration;
    let mut sum = Uint128::zero();
    for bucket in &history.buckets {
        if bucket.epoch >= epoch_min && bucket.epoch <= epoch_max {
            sum += bucket.amount;
        }
    }
    sum
}

// ============================================================
// Cross-contract helpers
// ============================================================

fn build_core_mint_msg(
    config: &MineConfig,
    to: String,
    amount: Uint128,
) -> Result<CosmosMsg, ContractError> {
    Ok(CosmosMsg::Wasm(WasmMsg::Execute {
        contract_addr: config.core_contract.to_string(),
        msg: cosmwasm_std::to_json_binary(&LitiumCoreExecuteMsg::Mint { to, amount })?,
        funds: vec![],
    }))
}

// ============================================================
// Proof Verification
// ============================================================

fn decode_hex_32(input: &str) -> Result<[u8; 32], ContractError> {
    let raw = hex::decode(input).map_err(|_| ContractError::InvalidHash {})?;
    if raw.len() != 32 {
        return Err(ContractError::InvalidHash {});
    }
    let mut out = [0u8; 32];
    out.copy_from_slice(&raw);
    Ok(out)
}

fn compute_pow_hash(challenge: &[u8; 32], nonce: u64) -> [u8; 32] {
    let input = uhash_core::build_input(challenge, nonce);
    uhash_core::hash(&input)
}

#[allow(clippy::too_many_arguments)]
fn verify_proof(
    nonce: u64,
    challenge_hex: &str,
    hash_hex: &str,
    required_difficulty: u32,
    max_proof_age: u64,
    proof_timestamp: u64,
    now_ts: u64,
) -> Result<(bool, u32), ContractError> {
    if proof_timestamp > now_ts {
        return Err(ContractError::TimestampInFuture {});
    }
    if now_ts.saturating_sub(proof_timestamp) > max_proof_age {
        return Err(ContractError::TimestampTooOld {
            max_age: max_proof_age,
        });
    }

    let challenge = decode_hex_32(challenge_hex)?;
    let computed = compute_pow_hash(&challenge, nonce);

    let provided = hex::decode(hash_hex).map_err(|_| ContractError::InvalidHash {})?;
    if provided.len() != 32 {
        return Err(ContractError::InvalidHash {});
    }
    if computed.as_slice() != provided.as_slice() {
        return Err(ContractError::HashMismatch {});
    }

    let difficulty_bits = count_leading_zero_bits(&computed);
    if difficulty_bits < required_difficulty {
        return Err(ContractError::InsufficientDifficulty {});
    }
    Ok((true, difficulty_bits))
}

fn count_leading_zero_bits(hash: &[u8; 32]) -> u32 {
    let mut zero_bits = 0u32;
    for byte in hash.iter() {
        if *byte == 0 {
            zero_bits += 8;
        } else {
            zero_bits += byte.leading_zeros();
            break;
        }
    }
    zero_bits
}

// ============================================================
// Admin Operations
// ============================================================

#[allow(clippy::too_many_arguments)]
fn execute_update_config(
    deps: DepsMut,
    info: MessageInfo,
    max_proof_age: Option<u64>,
    admin: Option<String>,
    estimated_gas_cost_uboot: Option<Uint128>,
    core_contract: Option<String>,
    stake_contract: Option<String>,
    refer_contract: Option<String>,
    warmup_base_rate: Option<Uint128>,
    pid_interval: Option<u64>,
    genesis_time: Option<u64>,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }

    if let Some(a) = max_proof_age {
        config.max_proof_age = a;
    }
    if let Some(a) = admin {
        config.admin = deps.api.addr_validate(&a)?;
    }
    if let Some(g) = estimated_gas_cost_uboot {
        config.estimated_gas_cost_uboot = g;
    }
    if let Some(c) = core_contract {
        config.core_contract = deps.api.addr_validate(&c)?;
    }
    if let Some(s) = stake_contract {
        config.stake_contract = deps.api.addr_validate(&s)?;
    }
    if let Some(r) = refer_contract {
        config.refer_contract = deps.api.addr_validate(&r)?;
    }
    if let Some(w) = warmup_base_rate {
        config.warmup_base_rate = w;
    }
    if let Some(p) = pid_interval {
        config.pid_interval = p;
    }
    if let Some(g) = genesis_time {
        config.genesis_time = g;
    }

    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("action", "update_config"))
}

fn execute_apply_testing_overrides(
    deps: DepsMut,
    info: MessageInfo,
    overrides: TestingOverrides,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }

    if let Some(a) = overrides.max_proof_age {
        config.max_proof_age = a;
    }
    CONFIG.save(deps.storage, &config)?;

    // Override windowed fees: spread the given amount across ALL buckets
    // so it appears in any windowed sum regardless of epoch range.
    if let Some(f) = overrides.override_windowed_fees {
        let window = SLIDING_WINDOW.load(deps.storage)?;
        let history = FEE_HISTORY.load(deps.storage)?;
        let duration = history.bucket_duration.max(1);
        // Place entire amount in one bucket whose epoch matches the window's t_last
        let target_epoch = window.t_last / duration;
        let num_buckets = history.buckets.len();
        let slot = (target_epoch as usize) % num_buckets.max(1);
        FEE_HISTORY.update(deps.storage, |mut h| -> StdResult<_> {
            for bucket in h.buckets.iter_mut() {
                *bucket = FeeBucket {
                    epoch: 0,
                    amount: Uint128::zero(),
                };
            }
            if !h.buckets.is_empty() {
                h.buckets[slot] = FeeBucket {
                    epoch: target_epoch,
                    amount: f,
                };
            }
            Ok(h)
        })?;
    }

    STATS.update(deps.storage, |mut stats| -> StdResult<_> {
        if let Some(total_proofs) = overrides.stats_total_proofs {
            stats.total_proofs = total_proofs;
        }
        if let Some(total_rewards) = overrides.stats_total_rewards {
            stats.total_rewards = total_rewards;
        }
        Ok(stats)
    })?;

    if overrides.window_count.is_some() || overrides.window_total_d.is_some() {
        SLIDING_WINDOW.update(deps.storage, |mut w| -> StdResult<_> {
            if let Some(c) = overrides.window_count {
                w.count = c;
            }
            if let Some(d) = overrides.window_total_d {
                w.total_d = d;
            }
            Ok(w)
        })?;
    }

    if overrides.pid_alpha.is_some() || overrides.pid_beta.is_some() {
        PID_STATE.update(deps.storage, |mut p| -> StdResult<_> {
            if let Some(a) = overrides.pid_alpha {
                p.alpha = a;
            }
            if let Some(b) = overrides.pid_beta {
                p.beta = b;
            }
            Ok(p)
        })?;
    }

    Ok(Response::new().add_attribute("action", "apply_testing_overrides"))
}

/// Full state reset for daily testing. Zeroes all runtime state back to
/// fresh-instantiation values while preserving config (except genesis_time).
fn execute_reset_state(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    genesis_time: Option<u64>,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }

    // Update genesis_time (default: current block time)
    config.genesis_time = genesis_time.unwrap_or(env.block.time.seconds());
    CONFIG.save(deps.storage, &config)?;

    // Reset stats
    STATS.save(
        deps.storage,
        &Stats {
            total_proofs: 0,
            total_rewards: Uint128::zero(),
            unique_miners: 0,
            total_difficulty_bits: 0,
        },
    )?;

    // Reset sliding window
    SLIDING_WINDOW.save(
        deps.storage,
        &SlidingWindow {
            entries: Vec::new(),
            head: 0,
            count: 0,
            total_d: 0,
            t_first: 0,
            t_last: 0,
        },
    )?;

    // Reset PID state
    PID_STATE.save(
        deps.storage,
        &PidState {
            alpha: 500_000,
            beta: 0,
            e_eff_prev: 0,
            e_cov_prev: 0,
            de_eff: 0,
            de_cov: 0,
            cached_staking_share: 0,
        },
    )?;

    // Reset fee history
    FEE_HISTORY.save(
        deps.storage,
        &FeeHistory {
            buckets: (0..config.fee_num_buckets)
                .map(|_| FeeBucket {
                    epoch: 0,
                    amount: Uint128::zero(),
                })
                .collect(),
            bucket_duration: config.fee_bucket_duration,
        },
    )?;

    // Clear proof prune cursor
    PROOF_PRUNE_CURSOR.remove(deps.storage);

    // Clear all miner stats (range remove)
    let miners: Vec<_> = MINER_STATS
        .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
        .collect::<Result<Vec<_>, _>>()?;
    for addr in &miners {
        MINER_STATS.remove(deps.storage, addr);
    }

    // Clear all used proof hashes
    let hashes: Vec<_> = USED_PROOF_HASHES
        .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
        .collect::<Result<Vec<_>, _>>()?;
    for hash in &hashes {
        USED_PROOF_HASHES.remove(deps.storage, hash);
    }

    Ok(Response::new()
        .add_attribute("action", "reset_state")
        .add_attribute("genesis_time", config.genesis_time.to_string())
        .add_attribute("miners_cleared", miners.len().to_string())
        .add_attribute("hashes_cleared", hashes.len().to_string()))
}

fn execute_pause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    config.paused = true;
    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("action", "pause"))
}

fn execute_unpause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    config.paused = false;
    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("action", "unpause"))
}

// ============================================================
// Migrate
// ============================================================

/// Legacy config for v0.7.0 migration (has min_difficulty field).
#[derive(serde::Serialize, serde::Deserialize)]
struct LegacyMineConfig {
    pub max_proof_age: u64,
    pub estimated_gas_cost_uboot: Uint128,
    pub core_contract: cosmwasm_std::Addr,
    pub stake_contract: cosmwasm_std::Addr,
    pub refer_contract: cosmwasm_std::Addr,
    pub token_contract: String,
    pub admin: cosmwasm_std::Addr,
    pub paused: bool,
    pub window_size: u32,
    pub pid_interval: u64,
    pub genesis_time: u64,
    pub warmup_base_rate: Uint128,
    pub min_difficulty: u32,
    pub fee_bucket_duration: u64,
    pub fee_num_buckets: u32,
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, ContractError> {
    let stored = get_contract_version(deps.storage)?;
    let stored_ver = stored
        .version
        .parse::<semver::Version>()
        .map_err(|_| ContractError::MigrationError {})?;
    let current_ver = CONTRACT_VERSION
        .parse::<semver::Version>()
        .map_err(|_| ContractError::MigrationError {})?;
    if stored_ver >= current_ver {
        return Err(ContractError::MigrationError {});
    }

    // Migrate config: remove deprecated min_difficulty field
    let raw = deps.storage.get(b"config");
    if let Some(data) = raw {
        let legacy: LegacyMineConfig =
            cosmwasm_std::from_json(&data).map_err(|_| ContractError::MigrationError {})?;
        let new_config = MineConfig {
            max_proof_age: legacy.max_proof_age,
            estimated_gas_cost_uboot: legacy.estimated_gas_cost_uboot,
            core_contract: legacy.core_contract,
            stake_contract: legacy.stake_contract,
            refer_contract: legacy.refer_contract,
            token_contract: legacy.token_contract,
            admin: legacy.admin,
            paused: legacy.paused,
            window_size: legacy.window_size,
            pid_interval: legacy.pid_interval,
            genesis_time: legacy.genesis_time,
            warmup_base_rate: legacy.warmup_base_rate,
            fee_bucket_duration: legacy.fee_bucket_duration,
            fee_num_buckets: legacy.fee_num_buckets,
        };
        CONFIG.save(deps.storage, &new_config)?;
    }

    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    Ok(Response::default().add_attribute("action", "migrate"))
}

// ============================================================
// Queries
// ============================================================

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::Config {} => to_json_binary(&query_config(deps, &env)?),
        QueryMsg::WindowStatus {} => to_json_binary(&query_window_status(deps, &env)?),
        QueryMsg::Stats {} => to_json_binary(&query_stats(deps)?),
        QueryMsg::MinerStats { address } => to_json_binary(&query_miner_stats(deps, address)?),
        QueryMsg::CalculateReward { difficulty_bits } => {
            to_json_binary(&query_calculate_reward(deps, env, difficulty_bits)?)
        }
        QueryMsg::EmissionInfo {} => to_json_binary(&query_emission_info(deps, &env)?),
    }
}

fn query_config(deps: Deps, env: &Env) -> StdResult<ConfigResponse> {
    let config = CONFIG.load(deps.storage)?;
    let pid = PID_STATE.load(deps.storage)?;
    let window = SLIDING_WINDOW.load(deps.storage)?;
    let fee_history = FEE_HISTORY.load(deps.storage)?;
    let now = env.block.time.seconds();
    let (_reward, base_rate) = compute_proof_reward(&config, &window, &pid, &fee_history, now, 1)
        .unwrap_or((Uint128::one(), config.warmup_base_rate));
    let min_profitable_d =
        compute_min_profitable_difficulty(config.estimated_gas_cost_uboot, base_rate);
    Ok(ConfigResponse {
        max_proof_age: config.max_proof_age,
        estimated_gas_cost_uboot: config.estimated_gas_cost_uboot,
        core_contract: config.core_contract.to_string(),
        stake_contract: config.stake_contract.to_string(),
        refer_contract: config.refer_contract.to_string(),
        token_contract: config.token_contract,
        admin: config.admin.to_string(),
        paused: config.paused,
        window_size: config.window_size,
        pid_interval: config.pid_interval,
        genesis_time: config.genesis_time,
        min_profitable_difficulty: min_profitable_d,
        alpha: pid.alpha,
        beta: pid.beta,
        fee_bucket_duration: config.fee_bucket_duration,
        fee_num_buckets: config.fee_num_buckets,
        warmup_base_rate: config.warmup_base_rate,
    })
}

fn query_window_status(deps: Deps, env: &Env) -> StdResult<WindowStatusResponse> {
    let config = CONFIG.load(deps.storage)?;
    let window = SLIDING_WINDOW.load(deps.storage)?;
    let pid = PID_STATE.load(deps.storage)?;
    let fee_history = FEE_HISTORY.load(deps.storage)?;
    let now = env.block.time.seconds();

    let window_entries = window.entries.len() as u32;
    let time_span = window.t_last.saturating_sub(window.t_first).max(1);
    let d_rate = window.total_d as f64 / time_span as f64;

    // Compute current base_rate (d=1 gives base_rate directly)
    let (_reward, base_rate) = compute_proof_reward(&config, &window, &pid, &fee_history, now, 1)
        .unwrap_or((Uint128::one(), config.warmup_base_rate));
    let min_profitable_d =
        compute_min_profitable_difficulty(config.estimated_gas_cost_uboot, base_rate);

    Ok(WindowStatusResponse {
        proof_count: window.count,
        window_d_rate: format!("{:.6}", d_rate),
        window_size: config.window_size,
        window_entries,
        base_rate,
        min_profitable_difficulty: min_profitable_d,
        alpha: format!("{:.6}", pid.alpha as f64 / MICROS as f64),
        beta: format!("{:.6}", pid.beta as f64 / MICROS as f64),
    })
}

fn query_stats(deps: Deps) -> StdResult<StatsResponse> {
    let stats = STATS.load(deps.storage)?;
    let avg_difficulty = if stats.total_proofs > 0 {
        (stats.total_difficulty_bits / stats.total_proofs) as u32
    } else {
        0
    };
    Ok(StatsResponse {
        total_proofs: stats.total_proofs,
        total_rewards: stats.total_rewards,
        unique_miners: stats.unique_miners,
        avg_difficulty,
    })
}

fn query_miner_stats(deps: Deps, address: String) -> StdResult<MinerStatsResponse> {
    let addr = deps.api.addr_validate(&address)?;
    let stats = MINER_STATS
        .may_load(deps.storage, &addr)?
        .unwrap_or(MinerStats {
            proofs_submitted: 0,
            total_rewards: Uint128::zero(),
            last_proof_time: 0,
        });
    Ok(MinerStatsResponse {
        address,
        proofs_submitted: stats.proofs_submitted,
        total_rewards: stats.total_rewards,
        last_proof_time: stats.last_proof_time,
    })
}

fn query_calculate_reward(
    deps: Deps,
    env: Env,
    difficulty_bits: u32,
) -> StdResult<RewardCalculationResponse> {
    let config = CONFIG.load(deps.storage)?;
    let window = SLIDING_WINDOW.load(deps.storage)?;
    let pid = PID_STATE.load(deps.storage)?;
    let fee_history = FEE_HISTORY.load(deps.storage)?;
    let now = env.block.time.seconds();

    let (gross_reward, _base_rate) =
        compute_proof_reward(&config, &window, &pid, &fee_history, now, difficulty_bits)
            .unwrap_or((Uint128::zero(), Uint128::zero()));

    Ok(RewardCalculationResponse {
        gross_reward,
        estimated_gas_cost_uboot: config.estimated_gas_cost_uboot,
        earns_reward: gross_reward >= Uint128::one(),
    })
}

fn query_emission_info(deps: Deps, env: &Env) -> StdResult<EmissionInfoResponse> {
    let config = CONFIG.load(deps.storage)?;
    let pid = PID_STATE.load(deps.storage)?;
    let window = SLIDING_WINDOW.load(deps.storage)?;
    let fee_history = FEE_HISTORY.load(deps.storage)?;
    let now = env.block.time.seconds();

    let e_rate = emission_rate_per_second(config.genesis_time, now);

    let time_span = window.t_last.saturating_sub(window.t_first).max(1);
    let windowed_fees = fee_history_windowed_sum(&fee_history, window.t_first, window.t_last);
    let fee_rate = if time_span > 0 {
        windowed_fees
            .checked_div(Uint128::from(time_span))
            .unwrap_or(Uint128::zero())
    } else {
        Uint128::zero()
    };

    let beta_complement = MICROS - pid.beta.min(MICROS);
    let fee_contribution = fee_rate.multiply_ratio(beta_complement, MICROS);
    let gross_rate = e_rate.checked_add(fee_contribution).unwrap_or(e_rate);

    let s_ratio = query_staking_ratio(&deps, &config).unwrap_or(0.0);
    let alpha_f = pid.alpha as f64 / MICROS as f64;
    let s_alpha = s_ratio.powf(alpha_f);
    let staking_share = (s_alpha * MICROS as f64).min(MICROS as f64) as u128;
    // Post-referral: 90% of gross split between stakers and miners (spec ยง5)
    let post_referral_rate =
        gross_rate.multiply_ratio(REFERRAL_SHARE_DEN - REFERRAL_SHARE_NUM, REFERRAL_SHARE_DEN);
    let staking_rate = post_referral_rate.multiply_ratio(staking_share, MICROS as u128);
    let mining_rate = post_referral_rate.saturating_sub(staking_rate);

    Ok(EmissionInfoResponse {
        alpha: pid.alpha,
        beta: pid.beta,
        emission_rate: e_rate,
        gross_rate,
        mining_rate,
        staking_rate,
        windowed_fees,
    })
}

Dimensions

cw-cyber/contracts/hub-networks/src/contract.rs
cw-cyber/contracts/std-test/src/contract.rs
cw-cyber/contracts/hub-skills/src/contract.rs
cw-cyber/contracts/litium-refer/src/contract.rs
cw-cyber/contracts/cybernet/src/contract.rs
cw-cyber/contracts/hub-libs/src/contract.rs
cw-cyber/contracts/litium-wrap/src/contract.rs
cw-cyber/contracts/litium-stake/src/contract.rs
cw-cyber/contracts/hub-protocols/src/contract.rs
cw-cyber/contracts/cw-cyber-passport/src/contract.rs
cw-cyber/contracts/graph-filter/src/contract.rs
cw-cyber/contracts/cw-cyber-subgraph/src/contract.rs
cw-cyber/contracts/litium-core/src/contract.rs
cw-cyber/contracts/hub-channels/src/contract.rs
cw-cyber/contracts/cw-cyber-gift/src/contract.rs
cw-cyber/contracts/hub-tokens/src/contract.rs

Local Graph