use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;

/// Price data for a financial instrument
///
/// Represents tick-level price data including bid/ask spreads,
/// last traded price, and basic OHLCV information.
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
pub struct PriceRecord {
    /// Unique identifier for this price record
    pub id: Uuid,
    /// Trading symbol (e.g., "AAPL", "BTC/USD")
    pub symbol: String,
    /// Timestamp when this price was recorded
    pub timestamp: DateTime<Utc>,
    /// Best bid price
    pub bid: Option<Decimal>,
    /// Best ask price
    pub ask: Option<Decimal>,
    /// Last traded price
    pub last: Option<Decimal>,
    /// Trading volume
    pub volume: Option<Decimal>,
    /// Opening price for the period
    pub open: Option<Decimal>,
    /// Highest price for the period
    pub high: Option<Decimal>,
    /// Lowest price for the period
    pub low: Option<Decimal>,
    /// Closing price for the period
    pub close: Option<Decimal>,
    /// Timestamp when this record was created in the database
    pub created_at: DateTime<Utc>,
}

impl PriceRecord {
    /// Create a new price record with the given symbol and timestamp
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol for the instrument
    /// * `timestamp` - When this price was recorded
    ///
    /// # Returns
    ///
    /// A new `PriceRecord` with all price fields set to `None`
    pub fn new(symbol: String, timestamp: DateTime<Utc>) -> Self {
        Self {
            id: Uuid::new_v4(),
            symbol,
            timestamp,
            bid: None,
            ask: None,
            last: None,
            volume: None,
            open: None,
            high: None,
            low: None,
            close: None,
            created_at: Utc::now(),
        }
    }

    /// Calculate mid price from bid and ask
    ///
    /// Returns the average of bid and ask prices if both are available.
    ///
    /// # Returns
    ///
    /// `Some(mid_price)` if both bid and ask are available, `None` otherwise
    pub fn mid_price(&self) -> Option<Decimal> {
        match (self.bid, self.ask) {
            (Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)),
            _ => None,
        }
    }

    /// Calculate spread from bid and ask
    ///
    /// Returns the difference between ask and bid prices.
    ///
    /// # Returns
    ///
    /// `Some(spread)` if both bid and ask are available, `None` otherwise
    pub fn spread(&self) -> Option<Decimal> {
        match (self.bid, self.ask) {
            (Some(bid), Some(ask)) => Some(ask - bid),
            _ => None,
        }
    }
}

/// Order book side enumeration
///
/// Represents which side of the order book a level belongs to.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, sqlx::Type, PartialEq, Eq, Hash)]
#[sqlx(type_name = "order_side", rename_all = "lowercase")]
pub enum BookSide {
    /// Bid side (buy orders)
    #[sqlx(rename = "bid")]
    Bid,
    /// Ask side (sell orders)
    #[sqlx(rename = "ask")]
    Ask,
}

/// Order book level data for database persistence
///
/// Represents a single level in the order book depth at a specific price point.
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
pub struct OrderBookLevelDb {
    /// Unique identifier for this order book level
    pub id: Uuid,
    /// Trading symbol
    pub symbol: String,
    /// Timestamp when this level was recorded
    pub timestamp: DateTime<Utc>,
    /// Which side of the book (bid or ask)
    pub side: BookSide,
    /// Price level
    pub price: Decimal,
    /// Total quantity available at this price level
    pub quantity: Decimal,
    /// Level depth (0 = best, 1 = second best, etc.)
    pub level: i32,
    /// Timestamp when this record was created in the database
    pub created_at: DateTime<Utc>,
}

impl OrderBookLevelDb {
    /// Create a new order book level
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol
    /// * `timestamp` - When this level was recorded
    ///
    /// * `side` - Which side of the book (bid or ask)
    /// * `price` - Price level
    ///
    /// * `quantity` - Quantity available at this price
    /// * `level` - Depth level (0 = best)
    ///
    /// # Returns
    ///
    /// A new `OrderBookLevelDb` instance
    pub fn new(
        symbol: String,
        timestamp: DateTime<Utc>,
        side: BookSide,
        price: Decimal,
        quantity: Decimal,
        level: i32,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            symbol,
            timestamp,
            side,
            price,
            quantity,
            level,
            created_at: Utc::now(),
        }
    }
}

/// Complete order book snapshot
///
/// Represents a full order book with all bid and ask levels at a specific point in time.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrderBook {
    /// Trading symbol
    pub symbol: String,
    /// Timestamp of this order book snapshot
    pub timestamp: DateTime<Utc>,
    /// All bid levels (buy orders) sorted by price descending
    pub bids: Vec<OrderBookLevelDb>,
    /// All ask levels (sell orders) sorted by price ascending
    pub asks: Vec<OrderBookLevelDb>,
}

impl OrderBook {
    pub fn new(symbol: String, timestamp: DateTime<Utc>) -> Self {
        Self {
            symbol,
            timestamp,
            bids: Vec::new(),
            asks: Vec::new(),
        }
    }

    /// Get the best bid price
    ///
    /// Returns the highest bid price (best buy price) from the order book.
    ///
    /// # Returns
    ///
    /// `Some(price)` if there are any bids, `None` if the bid side is empty
    pub fn best_bid(&self) -> Option<Decimal> {
        self.bids.first().map(|level| level.price)
    }

    /// Get the best ask price
    ///
    /// Returns the lowest ask price (best sell price) from the order book.
    ///
    /// # Returns
    ///
    /// `Some(price)` if there are any asks, `None` if the ask side is empty
    pub fn best_ask(&self) -> Option<Decimal> {
        self.asks.first().map(|level| level.price)
    }

    /// Calculate mid price from best bid and ask
    ///
    /// Returns the average of the best bid and best ask prices.
    ///
    /// # Returns
    ///
    /// `Some(mid_price)` if both best bid and ask are available, `None` otherwise
    pub fn mid_price(&self) -> Option<Decimal> {
        match (self.best_bid(), self.best_ask()) {
            (Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)),
            _ => None,
        }
    }

    /// Calculate spread between best bid and ask
    ///
    /// Returns the difference between the best ask and best bid prices.
    ///
    /// # Returns
    ///
    /// `Some(spread)` if both best bid and ask are available, `None` otherwise
    pub fn spread(&self) -> Option<Decimal> {
        match (self.best_bid(), self.best_ask()) {
            (Some(bid), Some(ask)) => Some(ask - bid),
            _ => None,
        }
    }
}

/// Technical indicator types
///
/// Enumeration of supported technical analysis indicators.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, sqlx::Type, PartialEq, Eq, Hash)]
#[sqlx(type_name = "indicator_type", rename_all = "lowercase")]
pub enum IndicatorType {
    /// Simple Moving Average
    #[sqlx(rename = "sma")]
    Sma,
    /// Exponential Moving Average
    #[sqlx(rename = "ema")]
    Ema,
    /// Relative Strength Index
    #[sqlx(rename = "rsi")]
    Rsi,
    /// Moving Average Convergence Divergence
    #[sqlx(rename = "macd")]
    Macd,
    /// Bollinger Bands
    #[sqlx(rename = "bollinger_bands")]
    BollingerBands,
    /// Stochastic Oscillator
    #[sqlx(rename = "stochastic")]
    Stochastic,
    /// Average True Range
    #[sqlx(rename = "atr")]
    Atr,
    /// Volume Weighted Average Price
    #[sqlx(rename = "volume_weighted_average_price")]
    VolumeWeightedAveragePrice,
}

/// Technical indicator data
///
/// Represents a computed technical indicator value at a specific point in time.
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
pub struct TechnicalIndicator {
    /// Unique identifier for this indicator record
    pub id: Uuid,
    /// Trading symbol this indicator applies to
    pub symbol: String,
    /// Type of technical indicator
    pub indicator_type: IndicatorType,
    /// Timestamp when this indicator value was computed
    pub timestamp: DateTime<Utc>,
    /// The computed indicator value
    pub value: Decimal,
    /// Parameters used for calculation (e.g., period, smoothing factor)
    pub parameters: serde_json::Value,
    /// Timestamp when this record was created in the database
    pub created_at: DateTime<Utc>,
}

impl TechnicalIndicator {
    /// Create a new technical indicator record
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol
    /// * `indicator_type` - Type of indicator
    ///
    /// * `timestamp` - When this indicator was computed
    /// * `value` - The computed indicator value
    ///
    /// * `parameters` - Parameters used for calculation
    ///
    /// # Returns
    ///
    /// A new `TechnicalIndicator` instance
    pub fn new(
        symbol: String,
        indicator_type: IndicatorType,
        timestamp: DateTime<Utc>,
        value: Decimal,
        parameters: serde_json::Value,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            symbol,
            indicator_type,
            timestamp,
            value,
            parameters,
            created_at: Utc::now(),
        }
    }
}

/// Time series aggregation periods
///
/// Standard time periods used for aggregating market data into candles.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TimePeriod {
    /// 1 second interval
    Second,
    /// 1 minute interval
    Minute,
    /// 5 minute interval
    FiveMinutes,
    /// 15 minute interval
    FifteenMinutes,
    /// 30 minute interval
    ThirtyMinutes,
    /// 1 hour interval
    Hour,
    /// 4 hour interval
    FourHours,
    /// 1 day interval
    Daily,
    /// 1 week interval
    Weekly,
    /// 1 month interval
    Monthly,
}

impl TimePeriod {
    /// Get the duration in seconds for this time period
    ///
    /// # Returns
    ///
    /// The number of seconds in this time period
    pub fn duration_seconds(&self) -> i64 {
        match self {
            TimePeriod::Second => 1_i64,
            TimePeriod::Minute => 60_i64,
            TimePeriod::FiveMinutes => 300_i64,
            TimePeriod::FifteenMinutes => 900_i64,
            TimePeriod::ThirtyMinutes => 1800_i64,
            TimePeriod::Hour => 3600_i64,
            TimePeriod::FourHours => 14400_i64,
            TimePeriod::Daily => 86400_i64,
            TimePeriod::Weekly => 604800_i64,
            TimePeriod::Monthly => 2592000_i64, // 30 days
        }
    }
}

/// OHLCV (Open, High, Low, Close, Volume) candle data
///
/// Represents aggregated price data for a specific time period.
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
pub struct Candle {
    /// Unique identifier for this candle
    pub id: Uuid,
    /// Trading symbol
    pub symbol: String,
    /// Time period as string (for database compatibility)
    pub period: String,
    /// Timestamp for the start of this candle period
    pub timestamp: DateTime<Utc>,
    /// Opening price for the period
    pub open: Decimal,
    /// Highest price during the period
    pub high: Decimal,
    /// Lowest price during the period
    pub low: Decimal,
    /// Closing price for the period
    pub close: Decimal,
    /// Total volume traded during the period
    pub volume: Decimal,
    /// Timestamp when this record was created in the database
    pub created_at: DateTime<Utc>,
}

impl Candle {
    /// Create a new candle
    ///
    /// # Arguments
    ///
    /// * `symbol` - Trading symbol
    /// * `period` - Time period for this candle
    ///
    /// * `timestamp` - Start time for this candle period
    /// * `open` - Opening price
    ///
    /// * `high` - Highest price
    /// * `low` - Lowest price
    ///
    /// * `close` - Closing price
    /// * `volume` - Total volume
    ///
    /// # Returns
    ///
    /// A new `Candle` instance
    pub fn new(
        symbol: String,
        period: TimePeriod,
        timestamp: DateTime<Utc>,
        open: Decimal,
        high: Decimal,
        low: Decimal,
        close: Decimal,
        volume: Decimal,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            symbol,
            period: format!("{:?}", period),
            timestamp,
            open,
            high,
            low,
            close,
            volume,
            created_at: Utc::now(),
        }
    }

    /// Calculate the range (high - low)
    ///
    /// Returns the difference between the highest and lowest prices.
    ///
    /// # Returns
    ///
    /// The price range for this candle
    pub fn range(&self) -> Decimal {
        self.high - self.low
    }

    /// Calculate the body (|close - open|)
    ///
    /// Returns the absolute difference between closing and opening prices.
    ///
    /// # Returns
    ///
    /// The body size of this candle
    pub fn body(&self) -> Decimal {
        (self.close - self.open).abs()
    }

    /// Check if candle is bullish (close > open)
    ///
    /// # Returns
    ///
    /// `true` if closing price is higher than opening price
    pub fn is_bullish(&self) -> bool {
        self.close > self.open
    }

    /// Check if candle is bearish (close < open)
    ///
    /// # Returns
    ///
    /// `true` if closing price is lower than opening price
    pub fn is_bearish(&self) -> bool {
        self.close < self.open
    }
}
