import asyncio
import multiprocessing

from decimal import Decimal
from typing import Dict, Optional, List

from pyqqq.brokerage.tracker import TradingTracker
from pyqqq.brokerage.kis.oauth import KISAuth
from pyqqq.brokerage.kis.simple import KISSimpleDomesticStock
from pyqqq.brokerage.ebest.oauth import EBestAuth
from pyqqq.brokerage.ebest.simple import EBestSimpleDomesticStock
from pyqqq.datatypes import *
from pyqqq.utils.logger import get_logger, logging

class TradingTrackerMultiProcessController:
    """
        부모 프로세스에서 생성되는 TradingTracker Controller
        메시지 큐를 통해 자식 프로세스에서 돌아가는 TradingTracker를 제어한다.
        자식 프로세스에서 listen한 order 이벤트는 event_snapshot_queue를 통해 받을 수 있다.
    """

    def __init__(
        self,
        auth_info: Dict[str, str] = None,
        fee_rate: Decimal = Decimal(0.00015),  # 뱅키스, LS증권 수수료율 0.015%
    ):

        self.auth_info = auth_info
        self.fee_rate = fee_rate
        self.event_snapshot_queue = multiprocessing.Queue()
        self.message_queue = multiprocessing.Queue()

    def start(self):
        print("process controller. start called")
        self.process = multiprocessing.Process(target=run_subprocess, args=(self.fee_rate, self.event_snapshot_queue, self.message_queue, self.auth_info))
        self.process.start()
    
    def stop(self):
        print("process controller. stop called")
        if self.process is not None:
            self.message_queue.put("stop")
            self.process.join()

            print("process controller. process joined")

    def get_event_snapshot(self):
        if not self.event_snapshot_queue.empty():
            return self.event_snapshot_queue.get()
        return None

class TradingTrackerMultiProcessWorker:
    """
        자식 프로세스에서만 생성되는 TradingTracker Worker     

    Args:
        event_snapshot_queue (multiprocessing.Queue): 거래 이벤트가 발생시, 부모 프로세스에 스냅샷을 전송하는 큐
        message_queue (multiprocessing.Queue): 부모 프로세스에서 명령을 전달할때 사용하는 큐
        fee_rate (Decimal): 증권사 수수료율 (기본값: 0.015%)
    """

    logger = get_logger("TradingTrackerMultiProcessWorker", level=logging.INFO)

    def __init__(
        self,
        event_snapshot_queue: multiprocessing.Queue,
        message_queue: multiprocessing.Queue,
        auth_info: Dict[str, str] = {},
        fee_rate: Decimal = Decimal(0.00015),  # 뱅키스, LS증권 수수료율 0.015%
    ):
        print("TradingTrackerMultiProcessWorker init")
        self.event_snapshot_queue = event_snapshot_queue
        self.message_queue = message_queue
        self.stop_event = asyncio.Event()

        # auth_info를 외부에서 넣어주는 경우
        if auth_info is not None:
            if "KIS_APP_KEY" in auth_info:
                self.auth = KISAuth(
                    appkey=auth_info["KIS_APP_KEY"],
                    appsecret=auth_info["KIS_APP_SECRET"],
                    paper_trading=auth_info["PAPER_TRADING"] == "1",
                )

                self.simple_api = KISSimpleDomesticStock(
                    auth=self.auth,
                    account_no=auth_info["KIS_CANO"],
                    account_product_code=auth_info["KIS_ACNT_PRDT_CD"],
                    hts_id=auth_info["KIS_HTS_ID"],
                )
            elif "EBEST_APP_KEY" in auth_info:
                self.auth = EBestAuth(
                    auth_info["EBEST_APP_KEY"],
                    auth_info["EBEST_APP_SECRET"],
                    paper_trading=auth_info["PAPER_TRADING"] == "1",
                )
                self.simple_api = EBestSimpleDomesticStock(auth=self.auth)

        else:
            if os.getenv("EBEST_APP_KEY") is not None and os.getenv("KIS_APP_KEY") is None:
                self.auth = EBestAuth(
                    os.getenv("EBEST_APP_KEY"),
                    os.getenv("EBEST_APP_SECRET"),
                    paper_trading=os.getenv("PAPER_TRADING") == "1",
                )
                self.simple_api = EBestSimpleDomesticStock(auth=self.auth)

            else:
                self.auth = KISAuth(
                    appkey=os.getenv("KIS_APP_KEY"),
                    appsecret=os.getenv("KIS_APP_SECRET"),
                    paper_trading=os.getenv("PAPER_TRADING") == "1",
                )

                self.simple_api = KISSimpleDomesticStock(
                    auth=self.auth,
                    account_no=os.getenv("KIS_CANO"),
                    account_product_code=os.getenv("KIS_ACNT_PRDT_CD"),
                    hts_id=os.getenv("KIS_HTS_ID"),
                )
        self.tracker = TradingTracker(self.simple_api, fee_rate=fee_rate)
        self.tracker.on_pending_order_update = self.on_pending_order_update

    async def start(self):
        """
        Tracker와 메시지 큐 리스너를 시작합니다
        """
        self.tasks = [
            asyncio.create_task(self.tracker.start()),
            asyncio.create_task(self._listen_message_queue()),
        ]
        await asyncio.gather(*self.tasks)

    async def stop(self):
        """
        Tracker와 메시지 큐 리스터를 중단합니다.
        """
        self.stop_event.set()
        await self.tracker.stop()
        for t in self.tasks:
            t.cancel()

        await asyncio.gather(*self.tasks)
        

    async def _listen_message_queue(self):
        """
        이벤트 루프에서 실행되는 함수. 외부에서 메시지 큐에 넣어준 명령을 수행한다.
        """
        self.logger.info(f"start _listen_message_queue")
        while not self.stop_event.is_set():
            if not self.message_queue.empty():
                message = self.message_queue.get()
                if message == "stop":
                    await self.stop()
                    return

            await asyncio.sleep(1)
        self.logger.info(f"end _listen_message_queue")


    def on_pending_order_update(
        self,
        status,
        event: OrderEvent,
    ):
        self.event_snapshot_queue.put(self._make_event_snapshot(status, event))

    def _make_event_snapshot(self, status, event: OrderEvent):
        """
        OrderEvent를 이벤트 스냅샷으로 변환합니다
        """
        
        return {
            "account_info": self.simple_api.get_account(),
            "pending_orders": self.tracker.pending_orders,
            "positions": self.tracker.positions,
            "status": status,
            "event": event,
        }

def run_subprocess(fee_rate, event_snapshot_queue, message_queue, auth_info=None):
    """
    args
    event_snapshot_queue: multiprocessing.Queue[Dict]
        거래 이벤트와 그 상태의 position, order, 계좌정보가 담겨있는 딕셔너리
    message_queue: multiprocessing.Queue[String]
        TradingTrackerMultiProcessWorker를 제어하는데 사용되는 메시지 큐
        
    """
    
    print(f"run_subprocess called")
    worker = TradingTrackerMultiProcessWorker(fee_rate=fee_rate, event_snapshot_queue=event_snapshot_queue, message_queue=message_queue, auth_info=auth_info)
    asyncio.run(worker.start())