use cosmwasm_std::{
    entry_point, to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo,
    Response, StdResult, Uint128, WasmMsg,
};
use cw2::{get_contract_version, set_contract_version};
use cw20::Cw20ReceiveMsg;
use cw20_base::state::{TokenInfo, BALANCES, TOKEN_INFO};

use crate::error::ContractError;
use crate::msg::{
    BurnStatsResponse, ConfigResponse, ExecuteMsg, InstantiateMsg, IsAuthorizedCallerResponse,
    QueryMsg, TestingOverrides, TotalMintedResponse,
};
use crate::state::{CoreConfig, AUTHORIZED_CALLERS, BURN_TOTAL, CONFIG, FEE_TOTAL, TOTAL_MINTED};

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

/// Total supply cap: 10^15 tokens * 10^6 decimals = 10^21 atomic units
pub const SUPPLY_CAP: u128 = 1_000_000_000_000_000_000_000u128;

// ============================================================
// 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());

    // Initialize CW-20 token info
    let token_info = TokenInfo {
        name: msg.name.clone(),
        symbol: msg.symbol.clone(),
        decimals: msg.decimals,
        total_supply: Uint128::zero(),
        mint: None,
    };
    TOKEN_INFO.save(deps.storage, &token_info)?;

    let mine_contract = msg
        .mine_contract
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?;
    let stake_contract = msg
        .stake_contract
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?;
    let refer_contract = msg
        .refer_contract
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?;
    let wrap_contract = msg
        .wrap_contract
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?;

    let config = CoreConfig {
        admin: admin.clone(),
        paused: false,
        mine_contract,
        stake_contract,
        refer_contract,
        wrap_contract,
    };
    CONFIG.save(deps.storage, &config)?;
    BURN_TOTAL.save(deps.storage, &Uint128::zero())?;
    FEE_TOTAL.save(deps.storage, &Uint128::zero())?;
    TOTAL_MINTED.save(deps.storage, &Uint128::zero())?;

    Ok(Response::new()
        .add_attribute("action", "instantiate")
        .add_attribute("name", msg.name)
        .add_attribute("symbol", msg.symbol)
        .add_attribute("decimals", msg.decimals.to_string())
        .add_attribute("admin", 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::Transfer { recipient, amount } => {
            execute_transfer(deps, info, recipient, amount)
        }
        ExecuteMsg::TransferFrom {
            owner,
            recipient,
            amount,
        } => execute_transfer_from(deps, env, info, owner, recipient, amount),
        ExecuteMsg::Send {
            contract,
            amount,
            msg: send_msg,
        } => execute_send(deps, info, contract, amount, send_msg),
        ExecuteMsg::SendFrom {
            owner,
            contract,
            amount,
            msg: send_msg,
        } => execute_send_from(deps, env, info, owner, contract, amount, send_msg),
        ExecuteMsg::Burn { amount } => execute_burn(deps, info, amount),
        ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
        ExecuteMsg::IncreaseAllowance {
            spender,
            amount,
            expires,
        } => execute_increase_allowance(deps, env, info, spender, amount, expires),
        ExecuteMsg::DecreaseAllowance {
            spender,
            amount,
            expires,
        } => execute_decrease_allowance(deps, env, info, spender, amount, expires),
        ExecuteMsg::Mint { to, amount } => execute_mint(deps, info, to, amount),
        ExecuteMsg::RegisterAuthorizedCaller { contract_addr } => {
            execute_register_authorized_caller(deps, info, contract_addr)
        }
        ExecuteMsg::RemoveAuthorizedCaller { contract_addr } => {
            execute_remove_authorized_caller(deps, info, contract_addr)
        }
        ExecuteMsg::UpdateConfig {
            admin,
            mine_contract,
            stake_contract,
            refer_contract,
            wrap_contract,
        } => execute_update_config(
            deps,
            info,
            admin,
            mine_contract,
            stake_contract,
            refer_contract,
            wrap_contract,
        ),
        ExecuteMsg::ApplyTestingOverrides { overrides } => {
            execute_apply_testing_overrides(deps, info, overrides)
        }
        ExecuteMsg::ResetState {} => execute_reset_state(deps, info),
        ExecuteMsg::Pause {} => execute_pause(deps, info),
        ExecuteMsg::Unpause {} => execute_unpause(deps, info),
        ExecuteMsg::Receive(_) => Ok(Response::new()),
    }
}

// ============================================================
// Burn computation
// ============================================================

/// Message type for litium-mine AccrueFees call.
#[derive(serde::Serialize)]
#[serde(rename_all = "snake_case")]
enum LitiumMineExecuteMsg {
    AccrueFees { amount: Uint128 },
}

/// Compute (net_amount, fee_amount) for a transfer.
/// Transfer is fee-exempt if sender OR recipient is one of the 4 spec-defined
/// contract slots (mine, stake, refer, wrap).
fn compute_fee(
    deps: &DepsMut,
    sender: &Addr,
    recipient: &Addr,
    amount: Uint128,
) -> Result<(Uint128, Uint128), ContractError> {
    let config = CONFIG.load(deps.storage)?;

    let is_exempt = |addr: &Addr| -> bool {
        config.mine_contract.as_ref() == Some(addr)
            || config.stake_contract.as_ref() == Some(addr)
            || config.refer_contract.as_ref() == Some(addr)
            || config.wrap_contract.as_ref() == Some(addr)
    };

    if is_exempt(sender) || is_exempt(recipient) {
        return Ok((amount, Uint128::zero()));
    }

    let fee = amount.multiply_ratio(1u128, 100u128);
    let net = amount.saturating_sub(fee);
    Ok((net, fee))
}

/// Apply fee: burn 1% permanently and notify litium-mine for PID tracking.
fn apply_fee(
    storage: &mut dyn cosmwasm_std::Storage,
    fee_amount: Uint128,
) -> Result<Option<CosmosMsg>, ContractError> {
    if fee_amount.is_zero() {
        return Ok(None);
    }

    // Burn fee permanently (reduce total supply per spec ยง4)
    TOKEN_INFO.update(storage, |mut info| -> StdResult<_> {
        info.total_supply = info.total_supply.checked_sub(fee_amount)?;
        Ok(info)
    })?;
    BURN_TOTAL.update(storage, |v| -> StdResult<_> { Ok(v + fee_amount) })?;
    FEE_TOTAL.update(storage, |v| -> StdResult<_> { Ok(v + fee_amount) })?;

    // Notify litium-mine of burned fee amount for PID controller tracking
    let config = CONFIG.load(storage)?;
    if let Some(ref mine_addr) = config.mine_contract {
        let msg = CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: mine_addr.to_string(),
            msg: cosmwasm_std::to_json_binary(&LitiumMineExecuteMsg::AccrueFees {
                amount: fee_amount,
            })?,
            funds: vec![],
        });
        return Ok(Some(msg));
    }

    Ok(None)
}

// ============================================================
// CW-20 Transfer / Send
// ============================================================

fn execute_transfer(
    deps: DepsMut,
    info: MessageInfo,
    recipient: String,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let rcpt_addr = deps.api.addr_validate(&recipient)?;
    let (net, fee) = compute_fee(&deps, &info.sender, &rcpt_addr, amount)?;

    if net.is_zero() {
        return Err(ContractError::InvalidTransferAmount {});
    }

    // Deduct full amount from sender
    BALANCES.update(
        deps.storage,
        &info.sender,
        |balance| -> StdResult<Uint128> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
    )?;

    // Credit net to recipient
    BALANCES.update(deps.storage, &rcpt_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default() + net)
    })?;

    let fee_msg = apply_fee(deps.storage, fee)?;

    let mut resp = Response::new()
        .add_attribute("action", "transfer")
        .add_attribute("from", info.sender.to_string())
        .add_attribute("to", rcpt_addr.to_string())
        .add_attribute("amount", amount.to_string());

    if !fee.is_zero() {
        resp = resp
            .add_attribute("fee", fee.to_string())
            .add_attribute("net", net.to_string());
    }
    if let Some(msg) = fee_msg {
        resp = resp.add_message(msg);
    }

    Ok(resp)
}

fn execute_transfer_from(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    owner: String,
    recipient: String,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let owner_addr = deps.api.addr_validate(&owner)?;
    let rcpt_addr = deps.api.addr_validate(&recipient)?;

    // Deduct allowance
    cw20_base::allowances::deduct_allowance(
        deps.storage,
        &owner_addr,
        &info.sender,
        &env.block,
        amount,
    )?;

    let (net, fee) = compute_fee(&deps, &owner_addr, &rcpt_addr, amount)?;
    if net.is_zero() {
        return Err(ContractError::InvalidTransferAmount {});
    }

    // Deduct full amount from owner
    BALANCES.update(deps.storage, &owner_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default().checked_sub(amount)?)
    })?;

    // Credit net to recipient
    BALANCES.update(deps.storage, &rcpt_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default() + net)
    })?;

    let fee_msg = apply_fee(deps.storage, fee)?;

    let mut resp = Response::new()
        .add_attribute("action", "transfer_from")
        .add_attribute("from", owner_addr.to_string())
        .add_attribute("by", info.sender.to_string())
        .add_attribute("to", rcpt_addr.to_string())
        .add_attribute("amount", amount.to_string());

    if !fee.is_zero() {
        resp = resp
            .add_attribute("fee", fee.to_string())
            .add_attribute("net", net.to_string());
    }
    if let Some(msg) = fee_msg {
        resp = resp.add_message(msg);
    }

    Ok(resp)
}

fn execute_send(
    deps: DepsMut,
    info: MessageInfo,
    contract: String,
    amount: Uint128,
    msg: Binary,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let contract_addr = deps.api.addr_validate(&contract)?;
    let (net, fee) = compute_fee(&deps, &info.sender, &contract_addr, amount)?;
    if net.is_zero() {
        return Err(ContractError::InvalidTransferAmount {});
    }

    // Deduct full amount from sender
    BALANCES.update(
        deps.storage,
        &info.sender,
        |balance| -> StdResult<Uint128> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
    )?;

    // Credit net to contract
    BALANCES.update(
        deps.storage,
        &contract_addr,
        |balance| -> StdResult<Uint128> { Ok(balance.unwrap_or_default() + net) },
    )?;

    let fee_msg = apply_fee(deps.storage, fee)?;

    let cw20_msg = Cw20ReceiveMsg {
        sender: info.sender.to_string(),
        amount: net,
        msg,
    };

    let mut resp = Response::new()
        .add_message(cw20_msg.into_cosmos_msg(contract_addr.to_string())?)
        .add_attribute("action", "send")
        .add_attribute("from", info.sender.to_string())
        .add_attribute("to", contract_addr.to_string())
        .add_attribute("amount", amount.to_string());

    if !fee.is_zero() {
        resp = resp
            .add_attribute("fee", fee.to_string())
            .add_attribute("net", net.to_string());
    }
    if let Some(msg) = fee_msg {
        resp = resp.add_message(msg);
    }

    Ok(resp)
}

fn execute_send_from(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    owner: String,
    contract: String,
    amount: Uint128,
    msg: Binary,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let owner_addr = deps.api.addr_validate(&owner)?;
    let contract_addr = deps.api.addr_validate(&contract)?;

    // Deduct allowance
    cw20_base::allowances::deduct_allowance(
        deps.storage,
        &owner_addr,
        &info.sender,
        &env.block,
        amount,
    )?;

    let (net, fee) = compute_fee(&deps, &owner_addr, &contract_addr, amount)?;
    if net.is_zero() {
        return Err(ContractError::InvalidTransferAmount {});
    }

    // Deduct full amount from owner
    BALANCES.update(deps.storage, &owner_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default().checked_sub(amount)?)
    })?;

    // Credit net to contract
    BALANCES.update(
        deps.storage,
        &contract_addr,
        |balance| -> StdResult<Uint128> { Ok(balance.unwrap_or_default() + net) },
    )?;

    let fee_msg = apply_fee(deps.storage, fee)?;

    let cw20_msg = Cw20ReceiveMsg {
        sender: owner_addr.to_string(),
        amount: net,
        msg,
    };

    let mut resp = Response::new()
        .add_message(cw20_msg.into_cosmos_msg(contract_addr.to_string())?)
        .add_attribute("action", "send_from")
        .add_attribute("from", owner_addr.to_string())
        .add_attribute("by", info.sender.to_string())
        .add_attribute("to", contract_addr.to_string())
        .add_attribute("amount", amount.to_string());

    if !fee.is_zero() {
        resp = resp
            .add_attribute("fee", fee.to_string())
            .add_attribute("net", net.to_string());
    }
    if let Some(msg) = fee_msg {
        resp = resp.add_message(msg);
    }

    Ok(resp)
}

// ============================================================
// Burn
// ============================================================

fn execute_burn(
    deps: DepsMut,
    info: MessageInfo,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    BALANCES.update(
        deps.storage,
        &info.sender,
        |balance| -> StdResult<Uint128> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
    )?;

    TOKEN_INFO.update(deps.storage, |mut info| -> StdResult<_> {
        info.total_supply = info.total_supply.checked_sub(amount)?;
        Ok(info)
    })?;

    BURN_TOTAL.update(deps.storage, |v| -> StdResult<_> { Ok(v + amount) })?;

    Ok(Response::new()
        .add_attribute("action", "burn")
        .add_attribute("from", info.sender.to_string())
        .add_attribute("amount", amount.to_string()))
}

fn execute_burn_from(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    owner: String,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let owner_addr = deps.api.addr_validate(&owner)?;

    // Deduct allowance
    cw20_base::allowances::deduct_allowance(
        deps.storage,
        &owner_addr,
        &info.sender,
        &env.block,
        amount,
    )?;

    BALANCES.update(deps.storage, &owner_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default().checked_sub(amount)?)
    })?;

    TOKEN_INFO.update(deps.storage, |mut info| -> StdResult<_> {
        info.total_supply = info.total_supply.checked_sub(amount)?;
        Ok(info)
    })?;

    BURN_TOTAL.update(deps.storage, |v| -> StdResult<_> { Ok(v + amount) })?;

    Ok(Response::new()
        .add_attribute("action", "burn_from")
        .add_attribute("from", owner_addr.to_string())
        .add_attribute("by", info.sender.to_string())
        .add_attribute("amount", amount.to_string()))
}

// ============================================================
// Allowances (delegate to cw20-base)
// ============================================================

fn execute_increase_allowance(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    spender: String,
    amount: Uint128,
    expires: Option<cw20::Expiration>,
) -> Result<Response, ContractError> {
    Ok(cw20_base::allowances::execute_increase_allowance(
        deps, env, info, spender, amount, expires,
    )?)
}

fn execute_decrease_allowance(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    spender: String,
    amount: Uint128,
    expires: Option<cw20::Expiration>,
) -> Result<Response, ContractError> {
    Ok(cw20_base::allowances::execute_decrease_allowance(
        deps, env, info, spender, amount, expires,
    )?)
}

// ============================================================
// Mint
// ============================================================

fn execute_mint(
    deps: DepsMut,
    info: MessageInfo,
    to: String,
    amount: Uint128,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if config.paused {
        return Err(ContractError::Paused {});
    }
    require_authorized_caller(deps.as_ref(), &info)?;

    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let to_addr = deps.api.addr_validate(&to)?;

    // Check supply cap
    let mut total_minted = TOTAL_MINTED.load(deps.storage)?;
    if total_minted + amount > Uint128::from(SUPPLY_CAP) {
        return Err(ContractError::SupplyCapExceeded {});
    }
    total_minted += amount;
    TOTAL_MINTED.save(deps.storage, &total_minted)?;

    // Update CW-20 balance and total_supply
    BALANCES.update(deps.storage, &to_addr, |balance| -> StdResult<Uint128> {
        Ok(balance.unwrap_or_default() + amount)
    })?;

    TOKEN_INFO.update(deps.storage, |mut token_info| -> StdResult<_> {
        token_info.total_supply += amount;
        Ok(token_info)
    })?;

    Ok(Response::new()
        .add_attribute("action", "mint")
        .add_attribute("to", to_addr.to_string())
        .add_attribute("amount", amount.to_string()))
}

// ============================================================
// Admin operations
// ============================================================

fn execute_register_authorized_caller(
    deps: DepsMut,
    info: MessageInfo,
    contract_addr: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    require_admin(&config, &info)?;

    let addr = deps.api.addr_validate(&contract_addr)?;
    AUTHORIZED_CALLERS.save(deps.storage, &addr, &true)?;

    Ok(Response::new()
        .add_attribute("action", "register_authorized_caller")
        .add_attribute("contract_addr", addr.to_string()))
}

fn execute_remove_authorized_caller(
    deps: DepsMut,
    info: MessageInfo,
    contract_addr: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    require_admin(&config, &info)?;

    let addr = deps.api.addr_validate(&contract_addr)?;
    AUTHORIZED_CALLERS.remove(deps.storage, &addr);

    Ok(Response::new()
        .add_attribute("action", "remove_authorized_caller")
        .add_attribute("contract_addr", addr.to_string()))
}

fn execute_update_config(
    deps: DepsMut,
    info: MessageInfo,
    admin: Option<String>,
    mine_contract: Option<String>,
    stake_contract: Option<String>,
    refer_contract: Option<String>,
    wrap_contract: Option<String>,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    require_admin(&config, &info)?;

    if let Some(a) = admin {
        config.admin = deps.api.addr_validate(&a)?;
    }
    if let Some(a) = mine_contract {
        config.mine_contract = Some(deps.api.addr_validate(&a)?);
    }
    if let Some(a) = stake_contract {
        config.stake_contract = Some(deps.api.addr_validate(&a)?);
    }
    if let Some(a) = refer_contract {
        config.refer_contract = Some(deps.api.addr_validate(&a)?);
    }
    if let Some(a) = wrap_contract {
        config.wrap_contract = Some(deps.api.addr_validate(&a)?);
    }
    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)?;
    require_admin(&config, &info)?;

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

    if let Some(total) = overrides.burn_total {
        BURN_TOTAL.save(deps.storage, &total)?;
    }
    if let Some(total) = overrides.fee_total {
        FEE_TOTAL.save(deps.storage, &total)?;
    }
    if let Some(total) = overrides.total_minted {
        TOTAL_MINTED.save(deps.storage, &total)?;
    }

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

/// Full state reset for daily testing. Zeroes all counters and CW-20 balances/supply.
fn execute_reset_state(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    require_admin(&config, &info)?;

    // Zero counters
    TOTAL_MINTED.save(deps.storage, &Uint128::zero())?;
    BURN_TOTAL.save(deps.storage, &Uint128::zero())?;
    FEE_TOTAL.save(deps.storage, &Uint128::zero())?;

    // Zero all CW-20 balances
    let holders: Vec<_> = BALANCES
        .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
        .collect::<Result<Vec<_>, _>>()?;
    for addr in &holders {
        BALANCES.remove(deps.storage, addr);
    }

    // Zero total supply
    TOKEN_INFO.update(deps.storage, |mut ti| -> StdResult<_> {
        ti.total_supply = Uint128::zero();
        Ok(ti)
    })?;

    Ok(Response::new()
        .add_attribute("action", "reset_state")
        .add_attribute("balances_cleared", holders.len().to_string()))
}

fn execute_pause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    require_admin(&config, &info)?;
    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)?;
    require_admin(&config, &info)?;
    config.paused = false;
    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("action", "unpause"))
}

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

#[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 {});
    }
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    // Initialize FEE_TOTAL if not present (migration from burn-based to fee-routing)
    if FEE_TOTAL.may_load(deps.storage)?.is_none() {
        FEE_TOTAL.save(deps.storage, &Uint128::zero())?;
    }

    Ok(Response::default())
}

// ============================================================
// Helpers
// ============================================================

fn require_admin(config: &CoreConfig, info: &MessageInfo) -> Result<(), ContractError> {
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    Ok(())
}

fn require_authorized_caller(deps: Deps, info: &MessageInfo) -> Result<(), ContractError> {
    let is_authorized = AUTHORIZED_CALLERS
        .may_load(deps.storage, &info.sender)?
        .unwrap_or(false);
    if !is_authorized {
        return Err(ContractError::NotAuthorizedCaller {
            addr: info.sender.to_string(),
        });
    }
    Ok(())
}

// ============================================================
// 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)?),
        QueryMsg::BurnStats {} => to_json_binary(&query_burn_stats(deps)?),
        QueryMsg::TotalMinted {} => to_json_binary(&query_total_minted(deps)?),
        QueryMsg::IsAuthorizedCaller { address } => {
            to_json_binary(&query_is_authorized_caller(deps, address)?)
        }
        QueryMsg::Balance { address } => {
            to_json_binary(&cw20_base::contract::query_balance(deps, address)?)
        }
        QueryMsg::TokenInfo {} => to_json_binary(&cw20_base::contract::query_token_info(deps)?),
        QueryMsg::Minter {} => to_json_binary(&query_minter()?),
        QueryMsg::Allowance { owner, spender } => to_json_binary(
            &cw20_base::allowances::query_allowance(deps, owner, spender)?,
        ),
        QueryMsg::AllAllowances {
            owner,
            start_after,
            limit,
        } => to_json_binary(&cw20_base::enumerable::query_owner_allowances(
            deps,
            owner,
            start_after,
            limit,
        )?),
        QueryMsg::AllSpenderAllowances {
            spender,
            start_after,
            limit,
        } => to_json_binary(&cw20_base::enumerable::query_spender_allowances(
            deps,
            spender,
            start_after,
            limit,
        )?),
        QueryMsg::AllAccounts { start_after, limit } => to_json_binary(
            &cw20_base::enumerable::query_all_accounts(deps, start_after, limit)?,
        ),
    }
}

fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
    let config = CONFIG.load(deps.storage)?;
    Ok(ConfigResponse {
        admin: config.admin.to_string(),
        paused: config.paused,
        mine_contract: config.mine_contract.map(|a| a.to_string()),
        stake_contract: config.stake_contract.map(|a| a.to_string()),
        refer_contract: config.refer_contract.map(|a| a.to_string()),
        wrap_contract: config.wrap_contract.map(|a| a.to_string()),
    })
}

fn query_burn_stats(deps: Deps) -> StdResult<BurnStatsResponse> {
    let total_burned = BURN_TOTAL
        .may_load(deps.storage)?
        .unwrap_or_else(Uint128::zero);
    // total_burned tracks explicit Burn calls (beta burn from litium-mine).
    // 1% transfer fees are now routed to litium-mine, tracked in FEE_TOTAL.
    Ok(BurnStatsResponse { total_burned })
}

fn query_total_minted(deps: Deps) -> StdResult<TotalMintedResponse> {
    let total_minted = TOTAL_MINTED
        .may_load(deps.storage)?
        .unwrap_or_else(Uint128::zero);
    Ok(TotalMintedResponse {
        total_minted,
        supply_cap: Uint128::from(SUPPLY_CAP),
    })
}

fn query_is_authorized_caller(
    deps: Deps,
    address: String,
) -> StdResult<IsAuthorizedCallerResponse> {
    let addr = deps.api.addr_validate(&address)?;
    let authorized = AUTHORIZED_CALLERS
        .may_load(deps.storage, &addr)?
        .unwrap_or(false);
    Ok(IsAuthorizedCallerResponse {
        address,
        authorized,
    })
}

fn query_minter() -> StdResult<Option<cw20::MinterResponse>> {
    // No single minter โ€” minting is managed via AUTHORIZED_CALLERS (mine, stake, refer).
    // Return None per CW-20 convention (matches cw20-base's optional minter).
    Ok(None)
}

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-mine/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/hub-channels/src/contract.rs
cw-cyber/contracts/cw-cyber-gift/src/contract.rs
cw-cyber/contracts/hub-tokens/src/contract.rs

Local Graph