from __future__ import annotations
from datetime import timedelta
from enum import Enum
from typing import Optional, Deque, cast, Any, Type

from spacepackets.ccsds import SpacePacket
from spacepackets.ecss import PusTelecommand
from tmtccmd.tc.procedure import TcProcedureBase


class TcQueueEntryType(Enum):
    PUS_TC = "pus-tc"
    CCSDS_TC = "ccsds-tc"
    RAW_TC = "raw-tc"
    CUSTOM = "custom"
    LOG = "log"
    WAIT = "wait"
    PACKET_DELAY = "set-delay"


class TcQueueEntryBase:
    """Generic TC queue entry abstraction. This allows filling the TC queue with custom objects"""

    def __init__(self, etype: TcQueueEntryType):
        self.etype = etype

    def is_tc(self) -> bool:
        """Check whether concrete object is an actual telecommand"""
        if (
            self.etype == TcQueueEntryType.PUS_TC
            or self.etype == TcQueueEntryType.RAW_TC
            or self.etype == TcQueueEntryType.CCSDS_TC
        ):
            return True
        return False


QueueDequeT = Deque[TcQueueEntryBase]


class PusTcEntry(TcQueueEntryBase):
    def __init__(self, pus_tc: PusTelecommand):
        super().__init__(TcQueueEntryType.PUS_TC)
        self.pus_tc = pus_tc

    def __repr__(self):
        return f"{self.__class__.__name__}({self.pus_tc!r})"


class SpacePacketEntry(TcQueueEntryBase):
    def __init__(self, space_packet: SpacePacket):
        super().__init__(TcQueueEntryType.CCSDS_TC)
        self.space_packet = space_packet

    def __repr__(self):
        return f"{self.__class__.__name__}({self.space_packet!r})"


class LogQueueEntry(TcQueueEntryBase):
    def __init__(self, log_str: str):
        super().__init__(TcQueueEntryType.LOG)
        self.log_str = log_str

    def __repr__(self):
        return f"{self.__class__.__name__}({self.log_str!r})"


class RawTcEntry(TcQueueEntryBase):
    def __init__(self, tc: bytes):
        super().__init__(TcQueueEntryType.RAW_TC)
        self.tc = tc

    def __repr__(self):
        return f"{self.__class__.__name__}({self.tc!r})"


class WaitEntry(TcQueueEntryBase):
    def __init__(self, wait_time: timedelta):
        super().__init__(TcQueueEntryType.WAIT)
        self.wait_time = wait_time

    @classmethod
    def from_millis(cls, millis: int) -> WaitEntry:
        return cls(timedelta(milliseconds=millis))

    def __repr__(self):
        return f"{self.__class__.__name__}({self.wait_time!r})"


class PacketDelayEntry(TcQueueEntryBase):
    def __init__(self, delay_time: timedelta):
        super().__init__(TcQueueEntryType.PACKET_DELAY)
        self.delay_time = delay_time

    @classmethod
    def from_millis(cls, millis: int) -> PacketDelayEntry:
        return cls(timedelta(milliseconds=millis))

    def __repr__(self):
        return f"{self.__class__.__name__}({self.delay_time!r})"


class QueueEntryHelper:
    def __init__(self, base: TcQueueEntryBase):
        self.base = base

    @property
    def is_tc(self) -> bool:
        return self.base.is_tc()

    @property
    def entry_type(self) -> TcQueueEntryType:
        return self.base.etype

    def __cast_internally(
        self,
        obj_type: Type[TcQueueEntryBase],
        obj: TcQueueEntryBase,
        expected_type: TcQueueEntryType,
    ) -> Any:
        if obj.etype != expected_type:
            raise TypeError(f"Invalid object {obj} for type {self.base.etype}")
        return cast(obj_type, obj)

    def to_log_entry(self) -> LogQueueEntry:
        return self.__cast_internally(LogQueueEntry, self.base, TcQueueEntryType.LOG)

    def to_pus_tc_entry(self) -> PusTcEntry:
        return self.__cast_internally(PusTcEntry, self.base, TcQueueEntryType.PUS_TC)

    def to_raw_tc_entry(self) -> RawTcEntry:
        return self.__cast_internally(RawTcEntry, self.base, TcQueueEntryType.RAW_TC)

    def to_wait_entry(self) -> WaitEntry:
        return self.__cast_internally(WaitEntry, self.base, TcQueueEntryType.WAIT)

    def to_packet_delay_entry(self) -> PacketDelayEntry:
        return self.__cast_internally(
            PacketDelayEntry, self.base, TcQueueEntryType.PACKET_DELAY
        )

    def to_space_packet_entry(self) -> SpacePacketEntry:
        return self.__cast_internally(
            SpacePacketEntry, self.base, TcQueueEntryType.CCSDS_TC
        )


class QueueWrapper:
    def __init__(
        self,
        info: Optional[TcProcedureBase],
        queue: Optional[QueueDequeT],
        inter_cmd_delay: timedelta = timedelta(milliseconds=0),
    ):
        self.info = info
        self.queue = queue
        self.inter_cmd_delay = inter_cmd_delay

    def __repr__(self):
        return (
            f"{self.__class__.__name__}(info={self.info!r}, queue={self.queue!r}, "
            f"inter_cmd_delay={self.inter_cmd_delay!r})"
        )


class QueueHelper:
    def __init__(self, queue_wrapper: QueueWrapper):
        self.queue_wrapper = queue_wrapper

    def __repr__(self):
        return f"{self.__class__.__name__}(queue_wrapper={self.queue_wrapper!r})"

    def add_log_cmd(self, print_str: str):
        self.queue_wrapper.queue.append(LogQueueEntry(print_str))

    def add_pus_tc(self, pus_tc: PusTelecommand):
        self.queue_wrapper.queue.append(PusTcEntry(pus_tc))

    def add_ccsds_tc(self, space_packet: SpacePacket):
        self.queue_wrapper.queue.append(SpacePacketEntry(space_packet))

    def add_raw_tc(self, tc: bytes):
        self.queue_wrapper.queue.append(RawTcEntry(tc))

    def add_wait(self, wait_time: timedelta):
        self.queue_wrapper.queue.append(WaitEntry(wait_time))

    def add_wait_ms(self, wait_ms: int):
        self.queue_wrapper.queue.append(WaitEntry.from_millis(wait_ms))

    def add_packet_delay(self, delay: timedelta):
        self.queue_wrapper.queue.append(PacketDelayEntry(delay))

    def add_packet_delay_ms(self, delay_ms: int):
        self.queue_wrapper.queue.append(PacketDelayEntry.from_millis(delay_ms))
