from datetime import datetime, timezone
from typing import Any, ClassVar, Dict, Optional

from sqlalchemy import ScalarResult, String, or_, select
from sqlalchemy.orm import Mapped, mapped_column

from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import ModelBase, SessionType


class PairLock(ModelBase):
    """
    Pair Locks database model.
    """

    __tablename__ = "pairlocks"
    session: ClassVar[SessionType]

    id: Mapped[int] = mapped_column(primary_key=True)

    pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True)
    # lock direction - long, short or * (for both)
    side: Mapped[str] = mapped_column(String(25), nullable=False, default="*")
    reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    # Time the pair was locked (start time)
    lock_time: Mapped[datetime] = mapped_column(nullable=False)
    # Time until the pair is locked (end time)
    lock_end_time: Mapped[datetime] = mapped_column(nullable=False, index=True)

    active: Mapped[bool] = mapped_column(nullable=False, default=True, index=True)

    def __repr__(self) -> str:
        lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT)
        lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT)
        return (
            f"PairLock(id={self.id}, pair={self.pair}, side={self.side}, lock_time={lock_time}, "
            f"lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})"
        )

    @staticmethod
    def query_pair_locks(
        pair: Optional[str], now: datetime, side: str = "*"
    ) -> ScalarResult["PairLock"]:
        """
        Get all currently active locks for this pair
        :param pair: Pair to check for. Returns all current locks if pair is empty
        :param now: Datetime object (generated via datetime.now(timezone.utc)).
        """
        filters = [
            PairLock.lock_end_time > now,
            # Only active locks
            PairLock.active.is_(True),
        ]
        if pair:
            filters.append(PairLock.pair == pair)
        if side != "*":
            filters.append(or_(PairLock.side == side, PairLock.side == "*"))
        else:
            filters.append(PairLock.side == "*")

        return PairLock.session.scalars(select(PairLock).filter(*filters))

    @staticmethod
    def get_all_locks() -> ScalarResult["PairLock"]:
        return PairLock.session.scalars(select(PairLock))

    def to_json(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "pair": self.pair,
            "lock_time": self.lock_time.strftime(DATETIME_PRINT_FORMAT),
            "lock_timestamp": int(self.lock_time.replace(tzinfo=timezone.utc).timestamp() * 1000),
            "lock_end_time": self.lock_end_time.strftime(DATETIME_PRINT_FORMAT),
            "lock_end_timestamp": int(
                self.lock_end_time.replace(tzinfo=timezone.utc).timestamp() * 1000
            ),
            "reason": self.reason,
            "side": self.side,
            "active": self.active,
        }
