use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use thiserror::Error;
use uuid::Uuid;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ExecutionVenue {
    Derive,
    Hyperliquid,
    Lighter,
    Bebop,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum InstrumentType {
    Option,
    Perp,
    Spot,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderSide {
    Buy,
    Sell,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OrderType {
    Market,
    Limit,
    PostOnlyLimit,
    Ioc,
    Fok,
    ReduceOnlyLimit,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TimeInForce {
    Gtc,
    Ioc,
    Fok,
    PostOnly,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequestMode {
    DryRun,
    Paper,
    Live,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ActionStatus {
    Received,
    Accepted,
    Filled,
    Canceled,
    Rejected,
    DryRun,
    Paper,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VenueCapabilities {
    pub venue: ExecutionVenue,
    pub instrument_types: Vec<InstrumentType>,
    pub order_types: Vec<OrderType>,
    pub supports_post_only: bool,
    pub supports_reduce_only: bool,
    pub supports_amend: bool,
    pub supports_mass_cancel: bool,
    pub supports_private_ws: bool,
    pub supports_quote_routing: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderIntent {
    pub request_id: String,
    pub account: String,
    pub venue: ExecutionVenue,
    pub instrument_type: InstrumentType,
    pub asset: String,
    pub symbol: String,
    pub side: OrderSide,
    pub order_type: OrderType,
    pub time_in_force: TimeInForce,
    pub price: Option<f64>,
    pub size: f64,
    pub notional_usd: Option<f64>,
    pub reduce_only: bool,
    pub mode: RequestMode,
}

impl OrderIntent {
    pub fn new(
        account: impl Into<String>,
        venue: ExecutionVenue,
        instrument_type: InstrumentType,
        asset: impl Into<String>,
        symbol: impl Into<String>,
        side: OrderSide,
        order_type: OrderType,
        size: f64,
    ) -> Self {
        Self {
            request_id: Uuid::new_v4().to_string(),
            account: account.into(),
            venue,
            instrument_type,
            asset: asset.into(),
            symbol: symbol.into(),
            side,
            order_type,
            time_in_force: TimeInForce::Gtc,
            price: None,
            size,
            notional_usd: None,
            reduce_only: false,
            mode: RequestMode::DryRun,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteRequest {
    pub request_id: String,
    pub account: String,
    pub venue: ExecutionVenue,
    pub asset: String,
    pub symbol: String,
    pub side: OrderSide,
    pub size: f64,
    pub notional_usd: Option<f64>,
    pub mode: RequestMode,
}

impl QuoteRequest {
    pub fn new(
        account: impl Into<String>,
        venue: ExecutionVenue,
        asset: impl Into<String>,
        symbol: impl Into<String>,
        side: OrderSide,
        size: f64,
    ) -> Self {
        Self {
            request_id: Uuid::new_v4().to_string(),
            account: account.into(),
            venue,
            asset: asset.into(),
            symbol: symbol.into(),
            side,
            size,
            notional_usd: None,
            mode: RequestMode::DryRun,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelRequest {
    pub request_id: String,
    pub account: String,
    pub venue: ExecutionVenue,
    pub venue_order_id: Option<String>,
    pub symbol: Option<String>,
    pub mode: RequestMode,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionResult {
    pub request_id: String,
    pub venue: ExecutionVenue,
    pub account: String,
    pub status: ActionStatus,
    pub mode: RequestMode,
    pub message: String,
    pub venue_order_id: Option<String>,
    pub payload: Value,
    pub emitted_at: DateTime<Utc>,
}

impl ActionResult {
    pub fn dry_run(intent: &OrderIntent, payload: Value) -> Self {
        Self {
            request_id: intent.request_id.clone(),
            venue: intent.venue,
            account: intent.account.clone(),
            status: ActionStatus::DryRun,
            mode: intent.mode,
            message: "validated".to_string(),
            venue_order_id: None,
            payload,
            emitted_at: Utc::now(),
        }
    }
}

#[derive(Debug, Error)]
pub enum ExecutionError {
    #[error("unsupported order type for venue")]
    UnsupportedOrderType,
    #[error("unsupported instrument type for venue")]
    UnsupportedInstrumentType,
    #[error("quote expired")]
    QuoteExpired,
    #[error("signing not configured")]
    SigningNotConfigured,
}

pub trait Signer: Send + Sync {
    fn sign(&self, payload: &str) -> Result<String, ExecutionError>;
}

pub fn dry_run_payload(label: &str, fields: &[(&str, Value)]) -> Value {
    let mut map = serde_json::Map::new();
    map.insert("label".to_string(), json!(label));
    for (key, value) in fields {
        map.insert((*key).to_string(), value.clone());
    }
    Value::Object(map)
}
