use barter::{
    Timed,
    engine::{
        Processor,
        state::{
            instrument::data::{DefaultInstrumentMarketData, InstrumentDataState},
            order::in_flight_recorder::InFlightRequestRecorder,
        },
    },
};
use barter_data::{
    books::Level,
    event::DataKind,
    subscription::book::OrderBookL1,
};
use barter_execution::{
    AccountEvent,
    order::request::{OrderRequestCancel, OrderRequestOpen},
};
use barter_instrument::{exchange::ExchangeIndex, instrument::InstrumentIndex};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};

use crate::strategy::indicators::RunningEma;

/// A completed candle with [`Decimal`] OHLCV fields for expression evaluation.
///
/// Converted once from `f64` at candle-close time so the expression evaluator
/// never needs to do repeated fallible `f64→Decimal` conversions.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CandleRecord {
    pub open: Decimal,
    pub high: Decimal,
    pub low: Decimal,
    pub close: Decimal,
    pub volume: Decimal,
    /// Candle open time, captured from the market event timestamp.
    /// `None` only for candles constructed in tests without a timestamp.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_time: Option<DateTime<Utc>>,
}

/// A closed OHLCV candle produced by [`CandleAggregator`].
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CompletedCandle {
    pub open_time: DateTime<Utc>,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
    pub trade_count: u64,
}

/// Aggregates individual trades into fixed-interval OHLCV candles for live mode.
///
/// Candle boundaries are aligned to UTC epoch (e.g. 1m candles close at :00, :01, ...).
/// Call [`push_trade`] for each incoming trade. When the return value is `Some`, a new
/// candle has just closed — update indicators and set `candle_ready = true`.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CandleAggregator {
    pub interval_secs: u64,
    pub current_slot: Option<DateTime<Utc>>,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
    pub trade_count: u64,
}

impl CandleAggregator {
    pub fn new(interval_secs: u64) -> Self {
        Self {
            interval_secs,
            current_slot: None,
            open: 0.0,
            high: 0.0,
            low: 0.0,
            close: 0.0,
            volume: 0.0,
            trade_count: 0,
        }
    }

    /// Feed a trade into the aggregator.
    ///
    /// Returns `Some(candle)` when the trade's timestamp crosses into a new candle
    /// interval, closing the previous candle. The returned candle's OHLCV reflects
    /// all trades up to (but not including) the current trade.
    pub fn push_trade(
        &mut self,
        price: f64,
        volume: f64,
        time: DateTime<Utc>,
    ) -> Option<CompletedCandle> {
        let slot = self.slot_for(time);
        match self.current_slot {
            None => {
                self.current_slot = Some(slot);
                self.open = price;
                self.high = price;
                self.low = price;
                self.close = price;
                self.volume = volume;
                self.trade_count = 1;
                None
            }
            Some(current) if current == slot => {
                if price > self.high { self.high = price; }
                if price < self.low { self.low = price; }
                self.close = price;
                self.volume += volume;
                self.trade_count += 1;
                None
            }
            Some(current) => {
                let closed = CompletedCandle {
                    open_time: current,
                    open: self.open,
                    high: self.high,
                    low: self.low,
                    close: self.close,
                    volume: self.volume,
                    trade_count: self.trade_count,
                };
                self.current_slot = Some(slot);
                self.open = price;
                self.high = price;
                self.low = price;
                self.close = price;
                self.volume = volume;
                self.trade_count = 1;
                Some(closed)
            }
        }
    }

    fn slot_for(&self, time: DateTime<Utc>) -> DateTime<Utc> {
        let ts = time.timestamp();
        let slot_ts = (ts / self.interval_secs as i64) * self.interval_secs as i64;
        DateTime::from_timestamp(slot_ts, 0).unwrap_or(time)
    }
}

/// Maximum number of candles/prices retained per timeframe.
const MAX_PRICE_HISTORY: usize = 500;

/// Custom instrument data that wraps [`DefaultInstrumentMarketData`] and adds
/// per-timeframe candle histories, price windows, EMA states, and aggregators.
///
/// ## Single-timeframe mode (backward compatible)
///
/// When only the primary timeframe is used (all strategies before multi-timeframe
/// support), behaviour is identical to before: one entry in each HashMap keyed by
/// `primary_interval_secs`. The `next_candle_interval_hint` field is unused.
///
/// ## Multi-timeframe mode
///
/// Additional timeframes are tracked in parallel. Candle histories for secondary
/// timeframes are updated when their candles close but do not trigger `candle_ready`.
/// The runner sets `next_candle_interval_hint` before each `DataKind::Candle` event
/// so the processor routes it to the correct per-timeframe history.
///
/// During live mode, multiple [`CandleAggregator`] instances run in parallel
/// (one per timeframe). Trades are fed to all aggregators simultaneously.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SwymInstrumentData {
    pub inner: DefaultInstrumentMarketData,
    pub last_order_time: Option<DateTime<Utc>>,
    pub last_event_time: Option<DateTime<Utc>>,
    /// The primary interval in seconds — the one that drives `candle_ready` and
    /// strategy evaluation cadence. `None` in tick mode.
    pub primary_interval_secs: Option<u64>,
    /// Set by the backtest runner before each `DataKind::Candle` event to route
    /// the candle to the correct per-timeframe history. When `None`, routes to
    /// the primary timeframe (or the only timeframe for single-TF backtests).
    pub next_candle_interval_hint: Option<u64>,
    /// Rolling price windows per interval_secs (most recent last), capped at
    /// [`MAX_PRICE_HISTORY`]. Used for RSI and Bollinger Band computations.
    pub trade_prices: HashMap<u64, VecDeque<Decimal>>,
    /// Running EMAs keyed by (interval_secs, period). Updated incrementally on
    /// each candle close for the given timeframe.
    pub ema_states: HashMap<(u64, usize), RunningEma>,
    /// Candle aggregators for live mode, keyed by interval_secs.
    pub candle_aggregators: HashMap<u64, CandleAggregator>,
    /// Set to `true` for the single `generate_algo_orders` call immediately after
    /// the PRIMARY candle closes. Cleared at the start of the next `process()` call.
    pub candle_ready: bool,
    /// Candle histories per interval_secs (most recent last), capped at
    /// [`MAX_PRICE_HISTORY`]. The primary timeframe and all additional timeframes
    /// each have their own entry.
    pub candle_histories: HashMap<u64, VecDeque<CandleRecord>>,
}

impl Default for SwymInstrumentData {
    fn default() -> Self {
        Self {
            inner: DefaultInstrumentMarketData::default(),
            last_order_time: None,
            last_event_time: None,
            primary_interval_secs: None,
            next_candle_interval_hint: None,
            trade_prices: HashMap::new(),
            ema_states: HashMap::new(),
            candle_aggregators: HashMap::new(),
            candle_ready: false,
            candle_histories: HashMap::new(),
        }
    }
}

impl InstrumentDataState for SwymInstrumentData {
    type MarketEventKind = DataKind;

    fn price(&self) -> Option<Decimal> {
        self.inner.price()
    }
}

impl<InstrumentKey> Processor<&barter_data::event::MarketEvent<InstrumentKey, DataKind>>
    for SwymInstrumentData
{
    type Audit = ();

    fn process(
        &mut self,
        event: &barter_data::event::MarketEvent<InstrumentKey, DataKind>,
    ) -> Self::Audit {
        // Clear the candle flag — valid only for the single generate_algo_orders
        // call immediately following a primary candle close.
        self.candle_ready = false;
        self.last_event_time = Some(event.time_exchange);

        match &event.kind {
            DataKind::Trade(trade) => {
                if self.candle_aggregators.is_empty() {
                    // Tick mode: update primary timeframe on every trade.
                    if let Some(price) = Decimal::from_f64_retain(trade.price) {
                        let primary = self.primary_interval_secs.unwrap_or(0);
                        self.push_price_for(primary, price);
                    }
                    self.inner.process(event);
                } else {
                    // Candle-aggregation mode: feed all aggregators.
                    let primary = self.primary_interval_secs;
                    // Collect (interval_secs, completed) without borrowing self.
                    let completions: Vec<(u64, CompletedCandle)> = self
                        .candle_aggregators
                        .iter_mut()
                        .filter_map(|(&secs, agg)| {
                            agg.push_trade(trade.price, trade.amount, event.time_exchange)
                                .map(|c| (secs, c))
                        })
                        .collect();

                    for (secs, completed) in completions {
                        if let Some(close) = Decimal::from_f64_retain(completed.close) {
                            self.push_price_for(secs, close);
                            if let (Some(open), Some(high), Some(low), Some(volume)) = (
                                Decimal::from_f64_retain(completed.open),
                                Decimal::from_f64_retain(completed.high),
                                Decimal::from_f64_retain(completed.low),
                                Decimal::from_f64_retain(completed.volume),
                            ) {
                                self.push_candle_for(secs, CandleRecord {
                                    open, high, low, close, volume,
                                    open_time: Some(completed.open_time),
                                });
                            }
                            // Only the primary timeframe triggers evaluation.
                            if primary == Some(secs) {
                                self.candle_ready = true;
                            }
                        }
                    }

                    self.inner.process(event);
                }
            }
            DataKind::Candle(candle) => {
                // Pre-formed candle event (candle-based backtest replay).
                // The runner sets `next_candle_interval_hint` to indicate which
                // timeframe this candle belongs to.
                let hint_secs = self.next_candle_interval_hint
                    .or(self.primary_interval_secs)
                    .unwrap_or(0);
                let is_primary = self.primary_interval_secs
                    .map(|p| p == hint_secs)
                    .unwrap_or(true); // single-TF backward compat

                if let Some(close) = Decimal::from_f64_retain(candle.close) {
                    self.push_price_for(hint_secs, close);

                    if let (Some(open), Some(high), Some(low), Some(volume)) = (
                        Decimal::from_f64_retain(candle.open),
                        Decimal::from_f64_retain(candle.high),
                        Decimal::from_f64_retain(candle.low),
                        Decimal::from_f64_retain(candle.volume),
                    ) {
                        self.push_candle_for(hint_secs, CandleRecord {
                            open, high, low, close, volume,
                            open_time: Some(event.time_exchange),
                        });
                    }

                    if is_primary {
                        self.inner.last_traded_price =
                            Some(Timed::new(close, event.time_exchange));

                        let level = Level { price: close, amount: Decimal::ONE };
                        self.inner.l1 = OrderBookL1 {
                            last_update_time: event.time_exchange,
                            best_bid: Some(level),
                            best_ask: Some(level),
                        };

                        self.candle_ready = true;
                    }
                }
            }
            _ => {
                self.inner.process(event);
            }
        }
    }
}

impl<ExchangeKey, AssetKey, InstrumentKey>
    Processor<&AccountEvent<ExchangeKey, AssetKey, InstrumentKey>> for SwymInstrumentData
{
    type Audit = ();

    fn process(
        &mut self,
        event: &AccountEvent<ExchangeKey, AssetKey, InstrumentKey>,
    ) -> Self::Audit {
        self.inner.process(event)
    }
}

impl InFlightRequestRecorder<ExchangeIndex, InstrumentIndex> for SwymInstrumentData {
    fn record_in_flight_cancel(
        &mut self,
        _request: &OrderRequestCancel<ExchangeIndex, InstrumentIndex>,
    ) {
    }

    fn record_in_flight_open(
        &mut self,
        _request: &OrderRequestOpen<ExchangeIndex, InstrumentIndex>,
    ) {
        self.last_order_time = Some(self.last_event_time.unwrap_or_else(Utc::now));
    }
}

impl SwymInstrumentData {
    /// Create an instance for multi-timeframe live mode.
    /// The primary timeframe drives `candle_ready`; additional timeframes are tracked
    /// in parallel.
    pub fn with_multi_timeframes(primary_secs: u64, additional: &[u64]) -> Self {
        let mut s = Self {
            primary_interval_secs: Some(primary_secs),
            ..Self::default()
        };
        for &secs in std::iter::once(&primary_secs).chain(additional.iter()) {
            s.candle_aggregators.insert(secs, CandleAggregator::new(secs));
            s.candle_histories.insert(secs, VecDeque::new());
            s.trade_prices.insert(secs, VecDeque::new());
        }
        s
    }

    /// Initialise per-timeframe candle history and price history entries for a
    /// candle-based backtest (no aggregators needed).
    pub fn init_timeframes(&mut self, primary_secs: u64, additional: &[u64]) {
        self.primary_interval_secs = Some(primary_secs);
        for &secs in std::iter::once(&primary_secs).chain(additional.iter()) {
            self.candle_histories.entry(secs).or_insert_with(VecDeque::new);
            self.trade_prices.entry(secs).or_insert_with(VecDeque::new);
        }
    }

    /// Ensure a [`RunningEma`] for `(interval_secs, period)` exists and is being updated.
    /// Call during strategy initialisation for each (timeframe, period) pair needed.
    pub fn register_ema(&mut self, interval_secs: u64, period: usize) {
        self.ema_states
            .entry((interval_secs, period))
            .or_insert_with(|| RunningEma::new(period));
    }

    /// Return the candle history for a given interval_secs, or `None` if not initialised.
    pub fn candle_history(&self, interval_secs: u64) -> Option<&VecDeque<CandleRecord>> {
        self.candle_histories.get(&interval_secs)
    }

    /// Return the price history for a given interval_secs, or `None` if not initialised.
    pub fn trade_price_history(&self, interval_secs: u64) -> Option<&VecDeque<Decimal>> {
        self.trade_prices.get(&interval_secs)
    }

    fn push_price_for(&mut self, interval_secs: u64, price: Decimal) {
        let prices = self.trade_prices.entry(interval_secs).or_insert_with(VecDeque::new);
        prices.push_back(price);
        if prices.len() > MAX_PRICE_HISTORY {
            prices.pop_front();
        }
        // Update all registered EMAs for this timeframe.
        for ((secs, _period), ema) in self.ema_states.iter_mut() {
            if *secs == interval_secs {
                ema.update(price);
            }
        }
    }

    fn push_candle_for(&mut self, interval_secs: u64, candle: CandleRecord) {
        let history = self.candle_histories.entry(interval_secs).or_insert_with(VecDeque::new);
        history.push_back(candle);
        if history.len() > MAX_PRICE_HISTORY {
            history.pop_front();
        }
    }

    /// Feed a single historical candle into the indicator state without triggering
    /// `candle_ready` or updating barter's internal `last_traded_price` / L1 book.
    ///
    /// Called by the backtest runner during the warmup pre-pass (candles before
    /// `starts_at`) so that long-period indicators (e.g. SMA(200) on 4h) are fully
    /// populated before the first bar of the user-requested backtest window.
    #[allow(clippy::too_many_arguments)]
    pub fn process_candle_warmup(
        &mut self,
        interval_secs: u64,
        open_time: DateTime<Utc>,
        open: Decimal,
        high: Decimal,
        low: Decimal,
        close: Decimal,
        volume: Decimal,
    ) {
        self.push_price_for(interval_secs, close);
        self.push_candle_for(interval_secs, CandleRecord { open, high, low, close, volume, open_time: Some(open_time) });
    }
}
