//! Whitelist preconfirmation driver subcommand.

use std::path::PathBuf;

use alloy_primitives::Address;
use async_trait::async_trait;
use clap::Parser;
use driver::{DriverConfig, metrics::DriverMetrics};
use protocol::shasta::set_devnet_unzen_override;
use tracing::warn;
use whitelist_preconfirmation_driver::{
    NetworkConfig, RunnerConfig, WhitelistPreconfirmationDriverMetrics,
    WhitelistPreconfirmationDriverRunner,
};

use crate::{
    commands::{Subcommand, build_driver_config},
    error::Result,
    flags::{common::CommonArgs, driver::DriverArgs, preconfirmation::PreconfirmationArgs},
};

/// Command-line interface for running the whitelist preconfirmation driver.
#[derive(Parser, Clone, Debug)]
#[command(about = "Runs the whitelist preconfirmation driver with whitelist P2P protocol")]
pub struct WhitelistPreconfirmationDriverSubCommand {
    /// Common CLI arguments shared across all subcommands.
    #[command(flatten)]
    pub common_flags: CommonArgs,
    /// Driver-specific CLI arguments.
    #[command(flatten)]
    pub driver_flags: DriverArgs,
    /// Preconfirmation-specific CLI arguments.
    #[command(flatten)]
    pub preconf_flags: PreconfirmationArgs,
    /// Shasta preconfirmation whitelist contract address.
    #[clap(long = "shasta.preconf-whitelist", env = "SHASTA_PRECONF_WHITELIST", required = true)]
    pub shasta_preconf_whitelist_address: Address,
    /// Optional listen address for the whitelist preconfirmation REST/WS server.
    #[clap(
        long = "preconfirmation.rpc-addr",
        visible_alias = "whitelist.rpc-addr",
        env = "PRECONFIRMATION_RPC_ADDR"
    )]
    pub preconfirmation_rpc_addr: Option<std::net::SocketAddr>,
    /// Optional path to JWT secret used to authenticate whitelist preconfirmation REST/WS calls.
    #[clap(long = "preconfirmation.jwt-secret", env = "PRECONFIRMATION_SERVER_JWT_SECRET")]
    pub preconfirmation_jwt_secret: Option<PathBuf>,
    /// Optional comma-separated list of CORS origins allowed by whitelist REST/WS server.
    #[clap(
        long = "preconfirmation.cors-origins",
        value_delimiter = ',',
        default_value = "*",
        env = "PRECONFIRMATION_SERVER_CORS_ORIGINS"
    )]
    pub preconfirmation_cors_origins: Vec<String>,
    /// Optional hex-encoded private key for P2P block signing.
    #[clap(long = "preconfirmation.p2p-signer-key", env = "PRECONFIRMATION_P2P_SIGNER_KEY")]
    pub preconfirmation_p2p_signer_key: Option<String>,
    /// Optional raw secp256k1 private key for the local P2P network identity.
    #[clap(long = "preconfirmation.p2p-priv-raw", env = "PRECONFIRMATION_P2P_PRIV_RAW")]
    pub preconfirmation_p2p_priv_raw: Option<String>,
}

impl WhitelistPreconfirmationDriverSubCommand {
    /// Build driver configuration from command-line arguments.
    fn build_driver_config(&self) -> Result<DriverConfig> {
        // Preconfirmation ingress is enabled so whitelist payload imports can reuse the
        // driver queue.
        build_driver_config(&self.common_flags, &self.driver_flags, true)
    }

    /// Build P2P configuration from command-line arguments.
    fn build_p2p_config(&self) -> Result<NetworkConfig> {
        let pre_dial_peers = self
            .preconf_flags
            .p2p_static_peers
            .iter()
            .filter_map(|peer| {
                peer.parse().map_err(|_| warn!(peer, "failed to parse static peer address")).ok()
            })
            .collect();

        let discovery_listen = (!self.preconf_flags.p2p_disable_discovery)
            .then_some(self.preconf_flags.p2p_discovery_addr);

        let config = NetworkConfig {
            listen_addr: self.preconf_flags.p2p_listen,
            advertise_addr: self.preconf_flags.p2p_advertise_addr,
            bootnodes: self.preconf_flags.p2p_bootnodes.clone(),
            pre_dial_peers,
            preconfirmation_p2p_key: NetworkConfig::parse_preconfirmation_p2p_priv_raw(
                self.preconfirmation_p2p_priv_raw.as_deref(),
            )?,
            discovery_listen,
            peers_lo: self.preconf_flags.p2p_peers_lo,
            peers_hi: self.preconf_flags.p2p_peers_hi,
        };
        config.validate()?;
        Ok(config)
    }

    /// Resolve the whitelist RPC listen address.
    fn resolve_rpc_addr(&self) -> Option<std::net::SocketAddr> {
        self.preconfirmation_rpc_addr
    }

    /// Resolve and parse optional JWT secret used by the whitelist REST/WS server.
    fn resolve_rpc_jwt_secret(&self) -> Result<Option<Vec<u8>>> {
        let Some(path) = self.preconfirmation_jwt_secret.as_ref() else {
            return Ok(None);
        };

        let secret = rpc::client::read_jwt_secret(path).ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                format!("failed to read preconfirmation JWT secret from {}", path.display()),
            )
        })?;

        Ok(Some(secret.as_bytes().to_vec()))
    }

    /// Resolve CORS origins for the whitelist REST/WS server.
    fn resolve_rpc_cors_origins(&self) -> Vec<String> {
        self.preconfirmation_cors_origins
            .iter()
            .filter_map(|origin| {
                let origin = origin.trim();
                (!origin.is_empty()).then(|| origin.to_string())
            })
            .collect::<Vec<_>>()
    }

    /// Run the whitelist preconfirmation driver.
    pub async fn run(&self) -> Result<()> {
        <Self as Subcommand>::run(self).await
    }
}

#[async_trait]
impl Subcommand for WhitelistPreconfirmationDriverSubCommand {
    /// Returns a reference to the common CLI arguments.
    fn common_args(&self) -> &CommonArgs {
        &self.common_flags
    }

    /// Registers driver metrics with the global registry.
    fn register_metrics(&self) -> Result<()> {
        DriverMetrics::init();
        WhitelistPreconfirmationDriverMetrics::init();
        Ok(())
    }

    /// Runs the whitelist preconfirmation driver.
    async fn run(&self) -> Result<()> {
        self.init_logs()?;
        set_devnet_unzen_override(self.common_flags.devnet_unzen_timestamp);
        self.init_metrics()?;

        let driver_config = self.build_driver_config()?;
        let p2p_config = self.build_p2p_config()?;

        let runner_config = RunnerConfig {
            driver_config,
            p2p_config,
            whitelist_address: self.shasta_preconf_whitelist_address,
            rpc_listen_addr: self.resolve_rpc_addr(),
            rpc_jwt_secret: self.resolve_rpc_jwt_secret()?,
            rpc_cors_origins: self.resolve_rpc_cors_origins(),
            p2p_signer_key: self.preconfirmation_p2p_signer_key.clone(),
        };

        WhitelistPreconfirmationDriverRunner::new(runner_config).run().await?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rejects_zero_peer_high_tide() {
        let config = NetworkConfig { peers_lo: 0, peers_hi: 0, ..Default::default() };
        let err = config.validate().expect_err("zero high tide must fail");
        assert!(err.to_string().contains("--p2p.peers.hi must be greater than zero"));
    }

    #[test]
    fn allows_zero_peer_low_tide() {
        NetworkConfig { peers_lo: 0, peers_hi: 1, ..Default::default() }
            .validate()
            .expect("zero low tide must remain valid");
    }
}
