from client.get import PublicClient
from ws_feeds.ws import MultiExchangeWebSocket
from client.client import OrderClient
from utils.utils import get_mins_in_timestamp
import logging
import asyncio




class TradingAlgorithm:
    def __init__(self, coin_base="BTC", warmup_period=100, api_key=None, secret_key=None, base_url=None):
        self.coin_base = coin_base
        self.warmup_period = warmup_period
        self.ws_client = MultiExchangeWebSocket(coin_base, warmup_period)
        self.order_client = OrderClient(api_key, secret_key, base_url)
        self.bsi_calculator = self.ws_client.bsi_calculator
        self.current_position = 0  # 0 for no position, 1 for long, -1 for short
        self.order_id = None
        self.market_data = None
        self.min_order_size = None
        self.fills = []
        self.positions = []
        self.current_position_size = 0
        self.last_trade_time = None
        self.trade_cooldown = 60  # 60 seconds cooldown before next trade

    async def run(self):
        """
        Runs the WebSocket connections, handles subscriptions, and executes trades.
        """
        await asyncio.gather(
            self.handle_websocket_feed(),
            self.execute_trades(),
            self.fetch_market_data(),
            self.fetch_positions(),
            self.fetch_fills()
        )

    async def handle_websocket_feed(self):
        """
        Handles the WebSocket feed and processes the incoming data.
        """
        await self.ws_client.subscribe()
        await self.ws_client.run()

    async def execute_trades(self):
        """
        Executes trades based on the signals generated by the BSICalculator.
        """
        while True:
            await asyncio.sleep(0)
            if len(self.bsi_calculator.trade_buffer) == self.warmup_period and self.market_data is not None:
                await self.process_signals()

    async def process_signals(self):
        """
        Processes the signals generated by the BSICalculator and executes the corresponding trades.
        """
        signals = self.bsi_calculator.generate_signals(self.bsi_calculator.compute_BSI(self.bsi_calculator.trade_buffer))
        for signal in signals:
            if signal == 1 and self.current_position != 1:
                if self.last_trade_time is None or (self.last_trade_time + self.trade_cooldown) < self.current_time():
                    await self.place_long_order()
                    self.current_position = 1
                    self.last_trade_time = self.current_time()
            elif signal == -1 and self.current_position != -1:
                if self.last_trade_time is None or (self.last_trade_time + self.trade_cooldown) < self.current_time():
                    await self.place_short_order()
                    self.current_position = -1
                    self.last_trade_time = self.current_time()
            elif signal == 0 and self.current_position != 0:
                if self.last_trade_time is None or (self.last_trade_time + self.trade_cooldown) < self.current_time():
                    await self.close_position()
                    self.current_position = 0
                    self.last_trade_time = self.current_time()

    def current_time(self):
        """
        Returns the current time.
        """
        return asyncio.get_event_loop().time()

    async def place_long_order(self):
        """
        Places a long order on the exchange.
        """
        try:
            order_response = await self.order_client.place_order(
                market_id=f"{self.coin_base}-USD",
                price=float(self.market_data["best_ask"]),
                side="long",
                size=.0001
            )
            # self.order_id = order_response["order_id"]
            logging.info(f"Placed long order: {order_response}")
        except Exception as e:
            logging.error(f"Error placing long order: {e}")

    async def place_short_order(self):
        """
        Places a short order on the exchange.
        """
        try:
            order_response = await self.order_client.place_order(
                market_id=f"{self.coin_base}-USD",
                price=float(self.market_data["best_bid"]),
                side="short",
                size=.0001
            )
            # self.order_id = order_response["order_id"]
            logging.info(f"Placed short order: {order_response}")
        except Exception as e:
            logging.error(f"Error placing short order: {e}")

    async def close_position(self):
        """
        Closes the current position.
        """
        try:
            if self.current_position == 1:
                order_response = await self.order_client.place_order(
                    market_id=f"{self.coin_base}-USD",
                    price=float(self.market_data["best_bid"]),
                    side="sell",
                    size=self.current_position_size
                )
            elif self.current_position == -1:
                order_response = await self.order_client.place_order(
                    market_id=f"{self.coin_base}-USD",
                    price=float(self.market_data["best_ask"]),
                    side="buy",
                    size=self.current_position_size
                )
            # self.order_id = order_response["order_id"]
            logging.info(f"Closed position: {order_response}")
        except Exception as e:
            logging.error(f"Error closing position: {e}")

    async def fetch_market_data(self):
        """
        Fetches the market data, including the best bid and ask prices, and the minimum order size.
        """
        while True:
            try:
                async with PublicClient(f"{self.coin_base}-USD") as client:
                    market_data = await client.markets()
                    if market_data["success"]:
                        self.market_data = market_data["result"][0]
                        self.min_order_size = float(self.market_data["min_order"])
                        logging.info(f"Fetched market data")
                    else:
                        logging.error(f"Error fetching market data: {market_data['error']}")
            except Exception as e:
                logging.error(f"Error fetching market data: {e}")
            await asyncio.sleep(8)

    async def fetch_positions(self):
        """
        Fetches the user's open positions.
        """
        while True:
            try:
                response = await self.order_client.send_request("/positions", {}, method="GET")
                if response["success"]:
                    self.positions = response["result"]
                    for position in self.positions:
                        if position["market_id"] == f"{self.coin_base}-USD":
                            self.current_position = 1 if position["side"] == "long" else -1
                            self.current_position_size = float(position["size"])
                    logging.info(f"Fetched positions: {self.positions}")
                else:
                    logging.error(f"Error fetching positions: {response['error']}")
            except Exception as e:
                logging.error(f"Error fetching positions: {e}")
            await asyncio.sleep(10)

    async def fetch_fills(self):
        """
        Fetches the user's trade fills.
        """
        while True:
            try:
                start_time = get_mins_in_timestamp()

                response = await self.order_client.send_request("/fills", {'market_id': 'BTC-USD','start_time':start_time}, method="GET")
                if response["success"]:
                    self.fills = response["result"]
                    logging.info(f"Fetched fills: {self.fills}")
                else:
                    logging.error(f"Error fetching fills: {response['error']}")
            except Exception as e:
                logging.error(f"Error fetching fills: {e}")
            await asyncio.sleep(4)
