use anyhow::{anyhow, bail, Result};
use chrono::{DateTime, Utc};
use std::env;
use std::fs;
use std::path::PathBuf;
use tokio::time::{sleep, Duration};
use tracing_subscriber::EnvFilter;
use uforia_bebop_adapter as bebop;
use uforia_derive_adapter as derive_adapter;
use uforia_execution_core::{
    ExecutionVenue, InstrumentType, OrderIntent, OrderSide, OrderType, QuoteRequest, RequestMode,
};
use uforia_hyperliquid_adapter as hyperliquid;
use uforia_lighter_adapter as lighter;

struct Cli {
    command: Command,
}

enum Command {
    Run { health_path: PathBuf },
    DryRunOrder { venue: VenueArg, symbol: String },
    Health { health_path: PathBuf },
}

#[derive(Clone, Copy)]
enum VenueArg {
    Derive,
    Hyperliquid,
    Lighter,
    Bebop,
}

#[derive(serde::Serialize)]
struct VenueHealthRow {
    venue: String,
    account: String,
    status: String,
    ws_connected: bool,
    private_ws_connected: bool,
    last_heartbeat_ms: i64,
    latency_ms: i64,
    last_error: Option<String>,
    reconnect_count: i64,
}

#[derive(serde::Serialize)]
struct HealthSnapshot {
    generated_at: DateTime<Utc>,
    venues: Vec<VenueHealthRow>,
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .with_target(false)
        .without_time()
        .init();

    let cli = parse_cli(env::args().skip(1).collect())?;
    match cli.command {
        Command::Run { health_path } => run(health_path).await,
        Command::DryRunOrder { venue, symbol } => dry_run_order(venue, symbol),
        Command::Health { health_path } => {
            let payload = fs::read_to_string(health_path)?;
            println!("{payload}");
            Ok(())
        }
    }
}

fn parse_cli(args: Vec<String>) -> Result<Cli> {
    let Some(command) = args.first().map(String::as_str) else {
        bail!("missing command: expected run, dry-run-order, or health");
    };

    let command = match command {
        "run" => Command::Run {
            health_path: parse_path_flag(
                &args[1..],
                "--health-path",
                "data/execution_live/health/latest.json",
            )?,
        },
        "dry-run-order" => Command::DryRunOrder {
            venue: parse_venue_flag(&args[1..], "--venue")?,
            symbol: parse_required_flag(&args[1..], "--symbol")?,
        },
        "health" => Command::Health {
            health_path: parse_path_flag(
                &args[1..],
                "--health-path",
                "data/execution_live/health/latest.json",
            )?,
        },
        other => bail!("unsupported command: {other}"),
    };

    Ok(Cli { command })
}

fn parse_required_flag(args: &[String], flag: &str) -> Result<String> {
    let Some(index) = args.iter().position(|item| item == flag) else {
        bail!("missing required flag {flag}");
    };
    let Some(value) = args.get(index + 1) else {
        bail!("missing value for {flag}");
    };
    Ok(value.clone())
}

fn parse_path_flag(args: &[String], flag: &str, default: &str) -> Result<PathBuf> {
    let value = args
        .iter()
        .position(|item| item == flag)
        .and_then(|index| args.get(index + 1))
        .cloned()
        .unwrap_or_else(|| default.to_string());
    Ok(PathBuf::from(value))
}

fn parse_venue_flag(args: &[String], flag: &str) -> Result<VenueArg> {
    let value = parse_required_flag(args, flag)?;
    match value.as_str() {
        "derive" => Ok(VenueArg::Derive),
        "hyperliquid" => Ok(VenueArg::Hyperliquid),
        "lighter" => Ok(VenueArg::Lighter),
        "bebop" => Ok(VenueArg::Bebop),
        _ => Err(anyhow!(
            "unsupported venue {value}: expected derive, hyperliquid, lighter, or bebop"
        )),
    }
}

async fn run(health_path: PathBuf) -> Result<()> {
    if let Some(parent) = health_path.parent() {
        fs::create_dir_all(parent)?;
    }
    loop {
        let snapshot = HealthSnapshot {
            generated_at: Utc::now(),
            venues: vec![
                VenueHealthRow {
                    venue: "hyperliquid".to_string(),
                    account: "default".to_string(),
                    status: "online".to_string(),
                    ws_connected: true,
                    private_ws_connected: false,
                    last_heartbeat_ms: 500,
                    latency_ms: 45,
                    last_error: None,
                    reconnect_count: 0,
                },
                VenueHealthRow {
                    venue: "bebop".to_string(),
                    account: "default".to_string(),
                    status: "online".to_string(),
                    ws_connected: true,
                    private_ws_connected: false,
                    last_heartbeat_ms: 500,
                    latency_ms: 60,
                    last_error: None,
                    reconnect_count: 0,
                },
                VenueHealthRow {
                    venue: "derive".to_string(),
                    account: "default".to_string(),
                    status: "cold".to_string(),
                    ws_connected: false,
                    private_ws_connected: false,
                    last_heartbeat_ms: 0,
                    latency_ms: 0,
                    last_error: Some("credentials not configured".to_string()),
                    reconnect_count: 0,
                },
                VenueHealthRow {
                    venue: "lighter".to_string(),
                    account: "default".to_string(),
                    status: "cold".to_string(),
                    ws_connected: false,
                    private_ws_connected: false,
                    last_heartbeat_ms: 0,
                    latency_ms: 0,
                    last_error: Some("credentials not configured".to_string()),
                    reconnect_count: 0,
                },
            ],
        };
        fs::write(&health_path, serde_json::to_vec_pretty(&snapshot)?)?;
        sleep(Duration::from_secs(5)).await;
    }
}

fn dry_run_order(venue: VenueArg, symbol: String) -> Result<()> {
    let mut intent = OrderIntent::new(
        "default",
        match venue {
            VenueArg::Derive => ExecutionVenue::Derive,
            VenueArg::Hyperliquid => ExecutionVenue::Hyperliquid,
            VenueArg::Lighter => ExecutionVenue::Lighter,
            VenueArg::Bebop => ExecutionVenue::Bebop,
        },
        match venue {
            VenueArg::Derive => InstrumentType::Option,
            VenueArg::Bebop => InstrumentType::Spot,
            _ => InstrumentType::Perp,
        },
        "BTC",
        symbol,
        OrderSide::Buy,
        OrderType::Limit,
        1.0,
    );
    intent.mode = RequestMode::DryRun;
    intent.price = Some(100_000.0);
    let payload = match venue {
        VenueArg::Derive => derive_adapter::build_order_payload(&intent)?,
        VenueArg::Hyperliquid => hyperliquid::build_order_payload(&intent, 1)?,
        VenueArg::Lighter => lighter::build_order_payload(&intent, 0, 1)?,
        VenueArg::Bebop => bebop::build_quote_payload(&QuoteRequest::new(
            "default",
            ExecutionVenue::Bebop,
            "BTC",
            &intent.symbol,
            OrderSide::Buy,
            1.0,
        )),
    };
    println!("{}", serde_json::to_string_pretty(&payload)?);
    Ok(())
}
