//! Synor blockchain node daemon.
//!
//! This is the main entry point for running a Synor node.

#![allow(dead_code)]

use std::path::PathBuf;
use std::sync::Arc;

use clap::{Parser, Subcommand};
use tracing::{error, info};

use synord::config::NodeConfig;
use synord::node::SynorNode;
use synord::services::StorageService;

/// Synor blockchain node daemon.
#[derive(Parser)]
#[command(name = "synord")]
#[command(version, about = "Synor blockchain node daemon", long_about = None)]
struct Cli {
    /// Configuration file path
    #[arg(short, long, default_value = "synord.toml")]
    config: PathBuf,

    /// Data directory
    #[arg(short, long, env = "SYNOR_DATA_DIR")]
    data_dir: Option<PathBuf>,

    /// Network to connect to
    #[arg(short, long, default_value = "mainnet")]
    network: String,

    /// Log level
    #[arg(long, default_value = "info")]
    log_level: String,

    /// Enable JSON logging
    #[arg(long)]
    json_logs: bool,

    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Run the node
    Run {
        /// Enable mining
        #[arg(long)]
        mine: bool,

        /// Mining address for block rewards
        #[arg(long)]
        coinbase: Option<String>,

        /// Number of mining threads (0 = auto)
        #[arg(long, default_value = "0")]
        mining_threads: usize,

        /// RPC bind address
        #[arg(long, default_value = "127.0.0.1")]
        rpc_host: String,

        /// RPC port
        #[arg(long, default_value = "16110")]
        rpc_port: u16,

        /// WebSocket port
        #[arg(long, default_value = "16111")]
        ws_port: u16,

        /// P2P bind address
        #[arg(long, default_value = "0.0.0.0")]
        p2p_host: String,

        /// P2P port
        #[arg(long, default_value = "16100")]
        p2p_port: u16,

        /// Seed nodes to connect to
        #[arg(long)]
        seeds: Vec<String>,
    },

    /// Initialize a new node
    Init {
        /// Network (mainnet, testnet, devnet)
        #[arg(long, default_value = "mainnet")]
        network: String,

        /// Force overwrite existing config
        #[arg(long)]
        force: bool,
    },

    /// Import blocks from file
    Import {
        /// Path to blocks file
        path: PathBuf,

        /// Skip verification
        #[arg(long)]
        no_verify: bool,
    },

    /// Export blocks to file
    Export {
        /// Output path
        path: PathBuf,

        /// Start height
        #[arg(long, default_value = "0")]
        from: u64,

        /// End height (0 = latest)
        #[arg(long, default_value = "0")]
        to: u64,
    },

    /// Show node version and info
    Version,
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();

    // Initialize logging
    init_logging(&cli.log_level, cli.json_logs);

    info!(
        version = env!("CARGO_PKG_VERSION"),
        "Starting Synor node daemon"
    );

    // Run command
    let result = match cli.command {
        Some(Commands::Run {
            mine,
            coinbase,
            mining_threads,
            rpc_host,
            rpc_port,
            ws_port,
            p2p_host,
            p2p_port,
            seeds,
        }) => {
            run_node(
                cli.config,
                cli.data_dir,
                cli.network,
                mine,
                coinbase,
                mining_threads,
                rpc_host,
                rpc_port,
                ws_port,
                p2p_host,
                p2p_port,
                seeds,
            )
            .await
        }

        Some(Commands::Init { network, force }) => init_node(cli.data_dir, network, force).await,

        Some(Commands::Import { path, no_verify }) => {
            import_blocks(cli.config, cli.data_dir, path, no_verify).await
        }

        Some(Commands::Export { path, from, to }) => {
            export_blocks(cli.config, cli.data_dir, path, from, to).await
        }

        Some(Commands::Version) => {
            print_version();
            Ok(())
        }

        None => {
            // Default to run
            run_node(
                cli.config,
                cli.data_dir,
                cli.network,
                false,
                None,
                0,
                "127.0.0.1".to_string(),
                16110,
                16111,
                "0.0.0.0".to_string(),
                16100,
                vec![],
            )
            .await
        }
    };

    if let Err(e) = result {
        error!("Node error: {}", e);
        std::process::exit(1);
    }
}

/// Initialize logging.
fn init_logging(level: &str, json: bool) {
    use tracing_subscriber::{fmt, prelude::*, EnvFilter};

    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));

    let subscriber = tracing_subscriber::registry().with(filter);

    if json {
        subscriber.with(fmt::layer().json()).init();
    } else {
        subscriber.with(fmt::layer().with_target(true)).init();
    }
}

/// Run the node.
#[allow(clippy::too_many_arguments)]
async fn run_node(
    config_path: PathBuf,
    data_dir: Option<PathBuf>,
    network: String,
    mine: bool,
    coinbase: Option<String>,
    mining_threads: usize,
    rpc_host: String,
    rpc_port: u16,
    ws_port: u16,
    p2p_host: String,
    p2p_port: u16,
    seeds: Vec<String>,
) -> anyhow::Result<()> {
    // Load or create config
    let config = NodeConfig::load_or_default(&config_path, &network)?;

    // Override with CLI args
    let config = config
        .with_data_dir(data_dir)
        .with_mining(mine, coinbase, mining_threads)
        .with_rpc(&rpc_host, rpc_port, ws_port)
        .with_p2p(&p2p_host, p2p_port, seeds);

    info!(
        network = %config.network,
        data_dir = %config.data_dir.display(),
        "Node configuration loaded"
    );

    // Create and start node
    let node = SynorNode::new(config).await?;
    let node = Arc::new(node);

    // Start all services
    node.start().await?;

    info!("Synor node is running");

    // Wait for shutdown signal
    wait_for_shutdown().await;

    info!("Shutting down...");
    node.stop().await?;

    info!("Node stopped gracefully");
    Ok(())
}

/// Initialize a new node with genesis block.
async fn init_node(data_dir: Option<PathBuf>, network: String, force: bool) -> anyhow::Result<()> {
    use synor_consensus::genesis::ChainConfig;
    use synor_storage::{BlockBody, ChainState};
    use synor_types::{BlockId, Network};

    let data_dir = data_dir.unwrap_or_else(default_data_dir);

    // Check if already initialized
    let genesis_marker = data_dir.join("chainstate").join("GENESIS");
    if genesis_marker.exists() && !force {
        anyhow::bail!(
            "Node already initialized at {}. Use --force to reinitialize.",
            data_dir.display()
        );
    }

    // Parse network
    let net = match network.as_str() {
        "mainnet" => Network::Mainnet,
        "testnet" => Network::Testnet,
        "devnet" => Network::Devnet,
        _ => anyhow::bail!(
            "Unknown network: {}. Use mainnet, testnet, or devnet.",
            network
        ),
    };

    info!(network = %network, "Initializing node...");

    // Get chain config with genesis block
    let chain_config = ChainConfig::for_network(net);

    info!(
        genesis_hash = %hex::encode(chain_config.genesis_hash.as_bytes()),
        "Using genesis block"
    );

    // Create directories
    std::fs::create_dir_all(&data_dir)?;
    std::fs::create_dir_all(data_dir.join("blocks"))?;
    std::fs::create_dir_all(data_dir.join("chainstate"))?;
    std::fs::create_dir_all(data_dir.join("contracts"))?;
    std::fs::create_dir_all(data_dir.join("keys"))?;

    // Create and save node config
    let config = NodeConfig::for_network(&network)?.with_data_dir(Some(data_dir.clone()));
    let config_path = data_dir.join("synord.toml");
    config.save(&config_path)?;

    info!("Created configuration file");

    // Initialize storage
    let storage = StorageService::new(&config).await?;
    storage.start().await?;

    info!("Initialized storage");

    // Store genesis block header
    storage.put_header(&chain_config.genesis.header).await?;
    info!("Stored genesis header");

    // Store genesis block body
    let genesis_hash = chain_config.genesis_hash;
    let body = BlockBody {
        transaction_ids: chain_config
            .genesis
            .body
            .transactions
            .iter()
            .map(|tx| tx.txid())
            .collect(),
    };
    storage.put_block_body(&genesis_hash, &body).await?;
    info!("Stored genesis block body");

    // Store genesis transactions
    for tx in &chain_config.genesis.body.transactions {
        storage.put_transaction(tx).await?;
    }
    info!(
        tx_count = chain_config.genesis.body.transactions.len(),
        "Stored genesis transactions"
    );

    // Set genesis hash in metadata
    let genesis_id = BlockId::from_bytes(*genesis_hash.as_bytes());
    storage.set_genesis(&genesis_id).await?;
    info!("Set genesis hash");

    // Set initial tips (just genesis)
    storage.set_tips(&[genesis_id]).await?;
    info!("Set initial tips");

    // Initialize chain state
    let chain_state = ChainState {
        max_blue_score: 0,
        total_blocks: 1,
        daa_score: 0,
        difficulty_bits: chain_config.initial_difficulty,
        total_work: vec![0; 32],
    };
    storage.set_chain_state(&chain_state).await?;
    info!("Initialized chain state");

    // Create genesis marker file
    std::fs::write(&genesis_marker, hex::encode(genesis_hash.as_bytes()))?;

    // Stop storage
    storage.stop().await?;

    info!(
        path = %data_dir.display(),
        network = %network,
        genesis = %hex::encode(genesis_hash.as_bytes()),
        "Node initialized successfully"
    );

    println!();
    println!("Synor node initialized!");
    println!();
    println!("  Network:     {}", network);
    println!("  Data dir:    {}", data_dir.display());
    println!("  Genesis:     {}", hex::encode(genesis_hash.as_bytes()));
    println!();
    println!("Chain parameters:");
    println!(
        "  Block time:       {} ms",
        chain_config.target_block_time_ms
    );
    println!("  GHOSTDAG K:       {}", chain_config.ghostdag_k);
    println!(
        "  Initial reward:   {} SYNOR",
        chain_config.initial_reward / 100_000_000
    );
    println!(
        "  Halving interval: {} blocks",
        chain_config.halving_interval
    );
    println!();
    println!("To start the node:");
    println!("  synord run --network {}", network);
    println!();

    Ok(())
}

/// Import blocks from file.
async fn import_blocks(
    config_path: PathBuf,
    data_dir: Option<PathBuf>,
    path: PathBuf,
    no_verify: bool,
) -> anyhow::Result<()> {
    use std::fs::File;
    use std::io::{BufReader, Read};

    let config = NodeConfig::load_or_default(&config_path, "mainnet")?;
    let config = config.with_data_dir(data_dir);

    info!(
        path = %path.display(),
        verify = !no_verify,
        "Importing blocks"
    );

    // Open the import file
    let file = File::open(&path)?;
    let mut reader = BufReader::new(file);

    // Read file header (magic + version)
    let mut magic = [0u8; 8];
    reader.read_exact(&mut magic)?;
    if &magic != b"SYNBLKS\x01" {
        anyhow::bail!("Invalid block export file format");
    }

    // Initialize storage
    let storage = Arc::new(StorageService::new(&config).await?);
    storage.start().await?;

    let mut imported = 0u64;
    let mut errors = 0u64;

    // Read blocks until EOF
    loop {
        // Read block length
        let mut len_buf = [0u8; 4];
        match reader.read_exact(&mut len_buf) {
            Ok(_) => {}
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
            Err(e) => return Err(e.into()),
        }
        let block_len = u32::from_le_bytes(len_buf) as usize;

        if block_len == 0 {
            break;
        }

        // Read block data
        let mut block_bytes = vec![0u8; block_len];
        reader.read_exact(&mut block_bytes)?;

        // Deserialize block data (hash + header + body)
        let block_data: synord::services::BlockData = match borsh::from_slice(&block_bytes) {
            Ok(b) => b,
            Err(e) => {
                error!("Failed to deserialize block: {}", e);
                errors += 1;
                continue;
            }
        };

        // Store the block
        if let Err(e) = storage.put_block(&block_data).await {
            error!(
                hash = hex::encode(&block_data.hash[..8]),
                "Failed to store block: {}", e
            );
            errors += 1;
        } else {
            imported += 1;
            if imported % 1000 == 0 {
                info!("Imported {} blocks...", imported);
            }
        }
    }

    storage.stop().await?;

    info!(
        imported = imported,
        errors = errors,
        "Block import complete"
    );

    Ok(())
}

/// Export blocks to file.
async fn export_blocks(
    config_path: PathBuf,
    data_dir: Option<PathBuf>,
    path: PathBuf,
    from: u64,
    to: u64,
) -> anyhow::Result<()> {
    use std::fs::File;
    use std::io::{BufWriter, Write};

    let config = NodeConfig::load_or_default(&config_path, "mainnet")?;
    let config = config.with_data_dir(data_dir);

    info!(
        path = %path.display(),
        from = from,
        to = to,
        "Exporting blocks"
    );

    // Initialize storage
    let storage = Arc::new(StorageService::new(&config).await?);
    storage.start().await?;

    // Get tips to start walking backwards through the DAG
    let tips = storage.get_tips().await?;
    if tips.is_empty() {
        anyhow::bail!("No tips found - is the node initialized?");
    }

    // Open output file
    let file = File::create(&path)?;
    let mut writer = BufWriter::new(file);

    // Write file header (magic + version)
    writer.write_all(b"SYNBLKS\x01")?;

    let mut exported = 0u64;
    let mut errors = 0u64;

    // Walk backwards from tips through the DAG
    // Export blocks with blue_score in [from, to] range
    let mut seen = std::collections::HashSet::new();
    let mut to_visit: Vec<[u8; 32]> = tips.into_iter().map(|h| *h.as_bytes()).collect();

    while let Some(hash) = to_visit.pop() {
        if seen.contains(&hash) {
            continue;
        }
        seen.insert(hash);

        if let Ok(Some(block_data)) = storage.get_block(&hash).await {
            // Parse header to check blue score (used as height in DAG)
            let header: synor_types::BlockHeader = match borsh::from_slice(&block_data.header) {
                Ok(h) => h,
                Err(e) => {
                    error!("Failed to parse header: {}", e);
                    errors += 1;
                    continue;
                }
            };

            let blue_score = header.blue_score.value();

            // Only export blocks within the specified blue score range
            if blue_score >= from && blue_score <= to {
                // Serialize the block data
                let serialized = borsh::to_vec(&block_data)?;

                // Write length + data
                writer.write_all(&(serialized.len() as u32).to_le_bytes())?;
                writer.write_all(&serialized)?;

                exported += 1;
                if exported % 1000 == 0 {
                    info!("Exported {} blocks...", exported);
                }
            }

            // Add parents to visit (walk backwards through DAG)
            // Only continue if we haven't gone below the 'from' threshold
            if blue_score > from {
                for parent in &header.parents {
                    to_visit.push(*parent.as_bytes());
                }
            }
        }
    }

    // Write terminator
    writer.write_all(&0u32.to_le_bytes())?;
    writer.flush()?;

    storage.stop().await?;

    info!(
        exported = exported,
        errors = errors,
        path = %path.display(),
        "Block export complete"
    );

    Ok(())
}

/// Print version information.
fn print_version() {
    println!("synord {}", env!("CARGO_PKG_VERSION"));
    println!();
    println!("Build info:");
    println!("  Rust version: {}", rustc_version());
    println!("  Target: {}", std::env::consts::ARCH);
    println!("  OS: {}", std::env::consts::OS);
    println!();
    println!("Network parameters:");
    println!("  Max supply: 70,000,000 SYNOR");
    println!("  Block time: ~1 second (DAG)");
    println!("  Algorithm: kHeavyHash PoW");
    println!("  Consensus: GHOSTDAG");
}

fn rustc_version() -> &'static str {
    option_env!("RUSTC_VERSION").unwrap_or("unknown")
}

/// Get default data directory.
fn default_data_dir() -> PathBuf {
    dirs::data_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("synor")
}

/// Wait for shutdown signal.
async fn wait_for_shutdown() {
    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};

        let mut sigterm = signal(SignalKind::terminate()).expect("Failed to register SIGTERM");
        let mut sigint = signal(SignalKind::interrupt()).expect("Failed to register SIGINT");

        tokio::select! {
            _ = sigterm.recv() => {
                info!("Received SIGTERM");
            }
            _ = sigint.recv() => {
                info!("Received SIGINT");
            }
        }
    }

    #[cfg(windows)]
    {
        tokio::signal::ctrl_c()
            .await
            .expect("Failed to listen for Ctrl+C");
        info!("Received Ctrl+C");
    }
}
