use crate::custody_context::NodeCustodyType;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::{collections::HashSet, sync::LazyLock, time::Duration};
use types::{Checkpoint, Hash256};

pub const DEFAULT_FORK_CHOICE_BEFORE_PROPOSAL_TIMEOUT: u64 = 250;

/// Default fraction of a slot lookahead for payload preparation (12/3 = 4 seconds on mainnet).
pub const DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR: u32 = 3;

/// Fraction of a slot lookahead for fork choice in the state advance timer (500ms on mainnet).
pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24;

/// Default sync tolerance epochs.
pub const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 2;

/// Invalid block root to be banned from processing and importing on Holesky network by default.
pub static INVALID_HOLESKY_BLOCK_ROOT: LazyLock<Hash256> = LazyLock::new(|| {
    Hash256::from_str("2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359")
        .expect("valid block root")
});

#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct ChainConfig {
    /// Maximum number of slots to skip when importing an attestation.
    ///
    /// If `None`, there is no limit.
    pub import_max_skip_slots: Option<u64>,
    /// A user-input `Checkpoint` that must exist in the beacon chain's sync path.
    ///
    /// If `None`, there is no weak subjectivity verification.
    pub weak_subjectivity_checkpoint: Option<Checkpoint>,
    /// Determine whether to reconstruct historic states, usually after a checkpoint sync.
    pub archive: bool,
    /// The max size of a message that can be sent over the network.
    pub max_network_size: usize,
    /// Number of milliseconds to wait for fork choice before proposing a block.
    ///
    /// If set to 0 then block proposal will not wait for fork choice at all.
    pub fork_choice_before_proposal_timeout_ms: u64,
    /// Number of skip slots in a row before the BN refuses to use connected builders during payload construction.
    pub builder_fallback_skips: usize,
    /// Number of skip slots in the past `SLOTS_PER_EPOCH` before the BN refuses to use connected
    /// builders during payload construction.
    pub builder_fallback_skips_per_epoch: usize,
    /// Number of epochs since finalization before the BN refuses to use connected builders during
    /// payload construction.
    pub builder_fallback_epochs_since_finalization: usize,
    /// Whether any chain health checks should be considered when deciding whether to use the builder API.
    pub builder_fallback_disable_checks: bool,
    /// When set to `true`, forget any valid/invalid/optimistic statuses in fork choice during start
    /// up.
    pub always_reset_payload_statuses: bool,
    /// Whether to apply paranoid checks to blocks proposed by this beacon node.
    pub paranoid_block_proposal: bool,
    /// Optionally set timeout for calls to checkpoint sync endpoint.
    pub checkpoint_sync_url_timeout: u64,
    /// The offset before the start of a proposal slot at which payload attributes should be sent.
    ///
    /// Low values are useful for execution engines which don't improve their payload after the
    /// first call, and high values are useful for ensuring the EL is given ample notice.
    pub prepare_payload_lookahead: Duration,
    /// Use EL-free optimistic sync for the finalized part of the chain.
    pub optimistic_finalized_sync: bool,
    /// The size of the shuffling cache,
    pub shuffling_cache_size: usize,
    /// If using a weak-subjectivity sync, whether we should download blocks all the way back to
    /// genesis.
    pub genesis_backfill: bool,
    /// EXPERIMENTAL: backfill blobs and data columns beyond the data availability window.
    pub complete_blob_backfill: bool,
    /// Whether to send payload attributes every slot, regardless of connected proposers.
    ///
    /// This is useful for block builders and testing.
    pub always_prepare_payload: bool,
    /// Number of epochs between each migration of data from the hot database to the freezer.
    pub epochs_per_migration: u64,
    /// When set to true Light client server computes and caches state proofs for serving updates
    pub enable_light_client_server: bool,
    /// The number of data columns to withhold / exclude from publishing when proposing a block.
    pub malicious_withhold_count: usize,
    /// Number of batches that the node splits blobs or data columns into during publication.
    /// This doesn't apply if the node is the block proposer. For PeerDAS only.
    pub blob_publication_batches: usize,
    /// The delay in milliseconds applied by the node between sending each blob or data column batch.
    /// This doesn't apply if the node is the block proposer.
    pub blob_publication_batch_interval: Duration,
    /// The max distance between the head block and the current slot at which Lighthouse will
    /// consider itself synced and still serve validator-related requests.
    pub sync_tolerance_epochs: u64,
    /// Artificial delay for block publishing. For PeerDAS testing only.
    pub block_publishing_delay: Option<Duration>,
    /// Artificial delay for data column publishing. For PeerDAS testing only.
    pub data_column_publishing_delay: Option<Duration>,
    /// Block roots of "banned" blocks which Lighthouse will refuse to import.
    ///
    /// On Holesky there is a block which is added to this set by default but which can be removed
    /// by using `--invalid-block-roots ""`.
    pub invalid_block_roots: HashSet<Hash256>,
    /// When set to true, the beacon node can be started even if the head state is outside the weak subjectivity period.
    pub ignore_ws_check: bool,
    /// Disable the getBlobs optimisation to fetch blobs from the EL mempool.
    pub disable_get_blobs: bool,
    /// Whether to enable partial data column support.
    pub enable_partial_columns: bool,
    /// The node's custody type, determining how many data columns to custody and sample.
    pub node_custody_type: NodeCustodyType,
    /// Disable proposer re-org
    pub disable_proposer_reorg: bool,
}

impl Default for ChainConfig {
    fn default() -> Self {
        Self {
            import_max_skip_slots: None,
            weak_subjectivity_checkpoint: None,
            archive: false,
            max_network_size: 10 * 1_048_576, // 10M
            fork_choice_before_proposal_timeout_ms: DEFAULT_FORK_CHOICE_BEFORE_PROPOSAL_TIMEOUT,
            // Builder fallback configs that are set in `clap` will override these.
            builder_fallback_skips: 3,
            builder_fallback_skips_per_epoch: 8,
            builder_fallback_epochs_since_finalization: 3,
            builder_fallback_disable_checks: false,
            always_reset_payload_statuses: false,
            paranoid_block_proposal: false,
            checkpoint_sync_url_timeout: 60,
            prepare_payload_lookahead: Duration::from_secs(4),
            // This value isn't actually read except in tests.
            optimistic_finalized_sync: true,
            shuffling_cache_size: crate::shuffling_cache::DEFAULT_CACHE_SIZE,
            genesis_backfill: false,
            complete_blob_backfill: false,
            always_prepare_payload: false,
            epochs_per_migration: crate::migrate::DEFAULT_EPOCHS_PER_MIGRATION,
            enable_light_client_server: true,
            malicious_withhold_count: 0,
            blob_publication_batches: 4,
            blob_publication_batch_interval: Duration::from_millis(300),
            sync_tolerance_epochs: DEFAULT_SYNC_TOLERANCE_EPOCHS,
            block_publishing_delay: None,
            data_column_publishing_delay: None,
            invalid_block_roots: HashSet::new(),
            ignore_ws_check: false,
            disable_get_blobs: false,
            enable_partial_columns: false,
            node_custody_type: NodeCustodyType::Fullnode,
            disable_proposer_reorg: false,
        }
    }
}
