use std::result;

use chrono::{DateTime, Duration, Utc};
use thiserror::Error;
use tokio::task::JoinError;

use crate::{
    db::error::DbError,
    signal::error::{SignalEvaluatorError, SignalOperatorError},
    sync::process::{
        sync_funding_settlements_task::error::SyncFundingSettlementsError,
        sync_price_history_task::error::SyncPriceHistoryError,
    },
};

use super::{
    super::error::{TradeCoreError, TradeExecutorError},
    config::MIN_BUFFER_SIZE,
    executor::error::SimulatedTradeExecutorError,
};

#[derive(Error, Debug)]
#[non_exhaustive]
pub enum BacktestError {
    #[error("[TaskJoin] {0}")]
    TaskJoin(JoinError),

    #[error("Backtest process was already consumed")]
    ProcessAlreadyConsumed,

    #[error("Buffer size must be at least {}, got {size}", MIN_BUFFER_SIZE)]
    InvalidConfigurationBufferSize { size: usize },

    #[error("Maximum running quantity must be at least 1, got {max}")]
    InvalidConfigurationMaxRunningQtd { max: usize },

    #[error(
        "Start and end times must be rounded to minutes. Start time: {start_time}, end time: {end_time}"
    )]
    InvalidTimeRangeNotRounded {
        start_time: DateTime<Utc>,
        end_time: DateTime<Utc>,
    },

    #[error(
        "Start time must be before the end time. Start time: {start_time}, end time: {end_time}"
    )]
    InvalidTimeRangeSequence {
        start_time: DateTime<Utc>,
        end_time: DateTime<Utc>,
    },

    #[error("Backtest duration must be at least {min_duration} day, got {duration_hours} hours")]
    InvalidTimeRangeTooShort {
        min_duration: Duration,
        duration_hours: i64,
    },

    #[error("Price History State Evaluation error: {0}")]
    PriceHistoryStateEvaluation(SyncPriceHistoryError),

    #[error("No price history entries found before start time")]
    DatabaseNoEntriesBeforeStartTime,

    #[error(
        "Required price history range including lookback period ({lookback_time} to {end_time}) is not available. History start {:?}, end: {:?}",
        history_start,
        history_end
    )]
    PriceHistoryUnavailable {
        lookback_time: DateTime<Utc>,
        end_time: DateTime<Utc>,
        history_start: Option<DateTime<Utc>>,
        history_end: Option<DateTime<Utc>>,
    },

    #[error("Buffer date calculation resulted in out of range value")]
    DateRangeBufferOutOfRange,

    #[error("Set trade executor error: {0}")]
    SetTradeExecutor(TradeCoreError),

    #[error("Operator error: {0}")]
    OperatorError(TradeCoreError),

    #[error("[Db] {0}")]
    Db(#[from] DbError),

    #[error(transparent)]
    SignalEvaluator(SignalEvaluatorError),

    #[error(transparent)]
    SignalOperator(SignalOperatorError),

    #[error("Signal processing error: {0}")]
    SignalProcessingError(TradeCoreError),

    #[error("Trade executor tick update error: {0}")]
    ExecutorTickUpdate(SimulatedTradeExecutorError),

    #[error("Trade executor time update error: {0}")]
    ExecutorTimeUpdate(SimulatedTradeExecutorError),

    #[error("Trade executor state evaluation error: {0}")]
    ExecutorStateEvaluation(TradeExecutorError),

    #[error("Unexpected empty buffer at {time}")]
    UnexpectedEmptyBuffer { time: DateTime<Utc> },

    #[error(
        "Out-of-order candle: received candle at {candle_time} but current bucket is at {bucket_time}"
    )]
    OutOfOrderCandle {
        candle_time: DateTime<Utc>,
        bucket_time: DateTime<Utc>,
    },

    #[error("No operators added to parallel backtest engine")]
    ParallelNoOperators,

    #[error("Operator name must not be empty")]
    ParallelEmptyOperatorName,

    #[error("Duplicate operator name: '{name}'")]
    ParallelDuplicateOperatorName { name: String },

    #[error("Operator '{operator_name}' failed: {source}")]
    ParallelOperatorFailed {
        operator_name: String,
        #[source]
        source: Box<BacktestError>,
    },

    #[error("Funding Settlements State Evaluation error: {0}")]
    FundingSettlementsStateEvaluation(SyncFundingSettlementsError),

    #[error(
        "Funding settlement data unavailable for backtest range ({from} to {to}). Settlement bounds: {bound_start:?} to {bound_end:?}."
    )]
    FundingSettlementDataUnavailable {
        from: DateTime<Utc>,
        to: DateTime<Utc>,
        bound_start: Option<DateTime<Utc>>,
        bound_end: Option<DateTime<Utc>>,
    },

    #[error("Funding settlement application error: {0}")]
    FundingSettlementApplication(SimulatedTradeExecutorError),
}

pub(super) type Result<T> = result::Result<T, BacktestError>;
