import logging
import time
from typing import Iterator, List

import t_tech
from t_tech.invest import (
    CandleInstrument,
    InvestError,
    MarketDataRequest,
    MarketDataResponse,
    SubscribeCandlesRequest,
    SubscriptionAction,
    SubscriptionInterval,
)
from t_tech.invest.services import Services
from t_tech.invest.strategies.base.account_manager import AccountManager
from t_tech.invest.strategies.base.errors import MarketDataNotAvailableError
from t_tech.invest.strategies.base.event import DataEvent, SignalEvent
from t_tech.invest.strategies.base.models import CandleEvent
from t_tech.invest.strategies.base.signal import CloseSignal, OpenSignal, Signal
from t_tech.invest.strategies.base.signal_executor_base import SignalExecutor
from t_tech.invest.strategies.base.trader_base import Trader
from t_tech.invest.strategies.moving_average.strategy import MovingAverageStrategy
from t_tech.invest.strategies.moving_average.strategy_settings import (
    MovingAverageStrategySettings,
)
from t_tech.invest.strategies.moving_average.strategy_state import (
    MovingAverageStrategyState,
)
from t_tech.invest.strategies.moving_average.supervisor import (
    MovingAverageStrategySupervisor,
)
from t_tech.invest.utils import floor_datetime, now

logger = logging.getLogger(__name__)


class MovingAverageStrategyTrader(Trader):
    def __init__(
        self,
        strategy: MovingAverageStrategy,
        settings: MovingAverageStrategySettings,
        services: Services,
        state: MovingAverageStrategyState,
        signal_executor: SignalExecutor,
        account_manager: AccountManager,
        supervisor: MovingAverageStrategySupervisor,
    ):
        super().__init__(strategy, services, settings)
        self._settings: MovingAverageStrategySettings = settings
        self._strategy = strategy
        self._services = services
        self._data: List[CandleEvent]
        self._market_data_stream: Iterator[MarketDataResponse]
        self._state = state
        self._signal_executor = signal_executor
        self._account_manager = account_manager
        self._supervisor = supervisor

        self._data = list(
            self._load_candles(
                (self._settings.short_period + self._settings.long_period) * 3
            )
        )
        for candle_event in self._data:
            self._supervisor.notify(self._convert_to_data_event(candle_event))
        self._strategy.fit(self._data)

        self._ensure_marginal_trade_active()
        self._subscribe()

    def _ensure_marginal_trade_active(self) -> None:
        self._account_manager.ensure_marginal_trade()

    def _subscribe(self):
        current_instrument = CandleInstrument(
            figi=self._settings.share_id,
            interval=SubscriptionInterval.SUBSCRIPTION_INTERVAL_ONE_MINUTE,
        )
        candle_subscribe_request = MarketDataRequest(
            subscribe_candles_request=SubscribeCandlesRequest(
                subscription_action=SubscriptionAction.SUBSCRIPTION_ACTION_SUBSCRIBE,
                instruments=[current_instrument],
            )
        )

        def request_iterator():
            yield candle_subscribe_request
            while True:
                time.sleep(1)

        self._market_data_stream = self._services.market_data_stream.market_data_stream(
            request_iterator()
        )

    def _is_candle_fresh(self, candle: t_tech.invest.Candle) -> bool:
        is_fresh_border = floor_datetime(
            now(), delta=self._settings.candle_interval_timedelta
        )
        logger.debug(
            "Checking if candle is fresh: candle.time=%s > is_fresh_border=%s  %s)",
            candle.time,
            is_fresh_border,
            candle.time >= is_fresh_border,
        )
        return candle.time >= is_fresh_border

    @staticmethod
    def _convert_to_data_event(candle_event: CandleEvent) -> DataEvent:
        return DataEvent(candle_event=candle_event, time=candle_event.time)

    def _make_observations(self) -> None:
        while True:
            market_data_response: MarketDataResponse = next(self._market_data_stream)
            logger.debug("got market_data_response: %s", market_data_response)
            if market_data_response.candle is None:
                logger.debug("market_data_response didn't have candle")
                continue
            candle = market_data_response.candle
            logger.debug("candle extracted: %s", candle)
            candle_event = self._convert_candle(candle)
            self._strategy.observe(candle_event)
            self._supervisor.notify(self._convert_to_data_event(candle_event))
            if self._is_candle_fresh(candle):
                logger.info("Data refreshed")
                break

    def _refresh_data(self) -> None:
        logger.info("Refreshing data")
        try:
            self._make_observations()
        except StopIteration as e:
            logger.info("Fresh quotations not available")
            raise MarketDataNotAvailableError() from e

    def _filter_closing_signals(self, signals: List[Signal]) -> List[Signal]:
        return list(filter(lambda signal: isinstance(signal, CloseSignal), signals))

    def _filter_opening_signals(self, signals: List[Signal]) -> List[Signal]:
        return list(filter(lambda signal: isinstance(signal, OpenSignal), signals))

    def _execute(self, signal: Signal) -> None:
        logger.info("Trying to execute signal %s", signal)
        try:
            self._signal_executor.execute(signal)  # type: ignore
        except InvestError:
            was_executed = False
        else:
            was_executed = True
        self._supervisor.notify(
            SignalEvent(signal=signal, was_executed=was_executed, time=now())
        )

    def _get_signals(self) -> List[Signal]:
        signals = list(self._strategy.predict())
        return [
            *self._filter_closing_signals(signals),
            *self._filter_opening_signals(signals),
        ]

    def trade(self) -> None:
        """Делает попытку следовать стратегии."""
        logger.info("Balance: %s", self._account_manager.get_current_balance())
        self._refresh_data()

        signals = self._get_signals()
        if signals:
            logger.info("Got signals %s", signals)
        for signal in signals:
            self._execute(signal)
        if self._state.position == 0:
            logger.info("Trade try complete")
