import os
import sys
from typing import Literal, Dict, List, TypedDict, NotRequired
from enum import Enum
from dynaconf import Dynaconf

BACKEND_LITERAL = Literal["memory", "redis"]


def is_sphinx_build():
    return "sphinx" in sys.modules


if not os.path.exists(".keys/"):
    os.makedirs(".keys/")
if not os.path.exists(".keys/.secrets.toml") and not is_sphinx_build():
    raise FileNotFoundError(
        "Config file not found, please create a config file at .keys/.secrets.toml"
    )


settings = Dynaconf(
    envvar_prefix="NEXUS",
    settings_files=[".keys/settings.toml", ".keys/.secrets.toml"],
    warn_dynaconf_global_settings=True,
    environments=False,
    load_dotenv=True,
)


def get_postgresql_config():
    """Get PostgreSQL configuration from environment variables or settings files."""
    return {
        "host": settings.get("PG_HOST", "localhost"),
        "port": settings.get("PG_PORT", 5432),
        "user": settings.get("PG_USER", "postgres"),
        "password": settings.get("PG_PASSWORD", ""),
        "database": settings.get("PG_DATABASE", "postgres"),
    }


def get_redis_config():
    try:
        return {
            "host": settings.REDIS_HOST,
            "port": settings.REDIS_PORT,
            "db": settings.REDIS_DB,
            "password": settings.REDIS_PASSWORD,
        }
    except Exception as e:
        raise ValueError(f"Failed to get Redis password: {e}")


IntervalType = Literal[
    "1s",
    "1m",
    "3m",
    "5m",
    "15m",
    "30m",
    "1h",
    "2h",
    "4h",
    "6h",
    "8h",
    "12h",
    "1d",
    "3d",
    "1w",
    "1M",
]


class BookLevel(Enum):
    L5 = "5"
    L10 = "10"
    L20 = "20"
    L50 = "50"


class KlineInterval(Enum):
    SECOND_1 = "1s"
    MINUTE_1 = "1m"
    MINUTE_3 = "3m"
    MINUTE_5 = "5m"
    MINUTE_15 = "15m"
    MINUTE_30 = "30m"
    HOUR_1 = "1h"
    HOUR_2 = "2h"
    HOUR_4 = "4h"
    HOUR_6 = "6h"
    HOUR_8 = "8h"
    HOUR_12 = "12h"
    DAY_1 = "1d"
    DAY_3 = "3d"
    WEEK_1 = "1w"
    MONTH_1 = "1M"
    VOLUME = "volume"

    @property
    def seconds(self) -> int:
        return {
            KlineInterval.SECOND_1: 1,
            KlineInterval.MINUTE_1: 60,
            KlineInterval.MINUTE_3: 180,
            KlineInterval.MINUTE_5: 300,
            KlineInterval.MINUTE_15: 900,
            KlineInterval.MINUTE_30: 1800,
            KlineInterval.HOUR_1: 3600,
            KlineInterval.HOUR_2: 7200,
            KlineInterval.HOUR_4: 14400,
            KlineInterval.HOUR_6: 21600,
            KlineInterval.HOUR_8: 28800,
            KlineInterval.HOUR_12: 43200,
            KlineInterval.DAY_1: 86400,
            KlineInterval.DAY_3: 259200,
            KlineInterval.WEEK_1: 604800,
            KlineInterval.MONTH_1: 2592000,
            KlineInterval.VOLUME: 0,
        }[self]

    @property
    def nanoseconds(self) -> int:
        return self.seconds * 1_000_000_000

    @property
    def microseconds(self) -> int:
        return self.seconds * 1_000_000

    @property
    def milliseconds(self) -> int:
        return self.seconds * 1_000


class SubmitType(Enum):
    CREATE = 0
    CANCEL = 1
    TWAP = 2
    CANCEL_TWAP = 3
    VWAP = 4
    CANCEL_VWAP = 5
    TAKE_PROFIT_AND_STOP_LOSS = 6
    MODIFY = 7
    CANCEL_ALL = 8
    BATCH = 9
    CREATE_WS = 10
    CANCEL_WS = 11


class EventType(Enum):
    BOOKL1 = 0
    TRADE = 1
    KLINE = 2
    MARK_PRICE = 3
    FUNDING_RATE = 4
    INDEX_PRICE = 5


class AlgoOrderStatus(Enum):
    RUNNING = "RUNNING"
    CANCELING = "CANCELING"
    FINISHED = "FINISHED"
    CANCELED = "CANCELED"
    FAILED = "FAILED"


class OrderStatus(Enum):
    # LOCAL
    INITIALIZED = "INITIALIZED"
    FAILED = "FAILED"
    CANCEL_FAILED = "CANCEL_FAILED"

    # IN-FLOW
    PENDING = "PENDING"
    CANCELING = "CANCELING"

    # OPEN
    ACCEPTED = "ACCEPTED"
    PARTIALLY_FILLED = "PARTIALLY_FILLED"

    # CLOSED
    FILLED = "FILLED"
    CANCELED = "CANCELED"
    EXPIRED = "EXPIRED"


class ExchangeType(Enum):
    BINANCE = "binance"
    OKX = "okx"
    BYBIT = "bybit"
    HYPERLIQUID = "hyperliquid"
    BITGET = "bitget"


class AccountType(Enum):
    pass


class OrderType(Enum):
    MARKET = "MARKET"
    LIMIT = "LIMIT"
    TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET"
    TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"
    STOP_LOSS_MARKET = "STOP_LOSS_MARKET"
    STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT"
    POST_ONLY = "POST_ONLY"

    @property
    def is_take_profit(self) -> bool:
        return self in (OrderType.TAKE_PROFIT_MARKET, OrderType.TAKE_PROFIT_LIMIT)

    @property
    def is_stop_loss(self) -> bool:
        return self in (OrderType.STOP_LOSS_MARKET, OrderType.STOP_LOSS_LIMIT)

    @property
    def is_market(self) -> bool:
        return self in (
            OrderType.MARKET,
            OrderType.TAKE_PROFIT_MARKET,
            OrderType.STOP_LOSS_MARKET,
        )

    @property
    def is_limit(self) -> bool:
        return self in (
            OrderType.LIMIT,
            OrderType.TAKE_PROFIT_LIMIT,
            OrderType.STOP_LOSS_LIMIT,
        )

    @property
    def is_post_only(self) -> bool:
        return self == OrderType.POST_ONLY


class TriggerType(Enum):
    LAST_PRICE = "LAST_PRICE"
    MARK_PRICE = "MARK_PRICE"
    INDEX_PRICE = "INDEX_PRICE"


class OrderSide(Enum):
    BUY = "BUY"
    SELL = "SELL"

    @property
    def is_buy(self) -> bool:
        return self == OrderSide.BUY

    @property
    def is_sell(self) -> bool:
        return self == OrderSide.SELL


class TimeInForce(Enum):
    GTC = "GTC"
    IOC = "IOC"
    FOK = "FOK"


class PositionSide(Enum):
    LONG = "LONG"
    SHORT = "SHORT"
    FLAT = "FLAT"

    @property
    def is_long(self) -> bool:
        return self == PositionSide.LONG

    @property
    def is_short(self) -> bool:
        return self == PositionSide.SHORT

    @property
    def is_flat(self) -> bool:
        return self == PositionSide.FLAT


class InstrumentType(Enum):
    SPOT = "spot"
    MARGIN = "margin"
    FUTURE = "future"
    OPTION = "option"
    SWAP = "swap"
    LINEAR = "linear"
    INVERSE = "inverse"


class OptionType(Enum):
    CALL = "call"
    PUT = "put"


STATUS_TRANSITIONS: Dict[OrderStatus, List[OrderStatus]] = {
    OrderStatus.PENDING: [
        OrderStatus.PENDING,
        OrderStatus.CANCELED,
        OrderStatus.CANCELING,
        OrderStatus.ACCEPTED,
        OrderStatus.PARTIALLY_FILLED,
        OrderStatus.FILLED,
        OrderStatus.CANCEL_FAILED,
        OrderStatus.FAILED,  # e.g., rejected by exchange we mark as failed
    ],
    OrderStatus.CANCELING: [
        OrderStatus.CANCELED,
        OrderStatus.PARTIALLY_FILLED,
        OrderStatus.FILLED,
    ],
    OrderStatus.ACCEPTED: [
        OrderStatus.ACCEPTED,  # for modify order, pending -> accepted -> accepted -> (not allowed) -> pending
        OrderStatus.PARTIALLY_FILLED,
        OrderStatus.FILLED,
        OrderStatus.CANCELING,
        OrderStatus.CANCELED,
        OrderStatus.EXPIRED,
        OrderStatus.CANCEL_FAILED,
    ],
    OrderStatus.PARTIALLY_FILLED: [
        OrderStatus.ACCEPTED,  # for modify order, accepted -> partially filled -> accepted -> (not allowed) -> pending
        OrderStatus.PARTIALLY_FILLED,
        OrderStatus.FILLED,
        OrderStatus.CANCELING,
        OrderStatus.CANCELED,
        OrderStatus.EXPIRED,
        OrderStatus.CANCEL_FAILED,
    ],
    OrderStatus.CANCEL_FAILED: [OrderStatus.FILLED],
    OrderStatus.FILLED: [],
    OrderStatus.CANCELED: [],
    OrderStatus.EXPIRED: [],
    OrderStatus.FAILED: [],
}


class DataType(Enum):
    BOOKL1 = "bookl1"
    BOOKL2 = "bookl2"
    TRADE = "trade"
    KLINE = "kline"
    VOLUME_KLINE = "volume_kline"
    MARK_PRICE = "mark_price"
    FUNDING_RATE = "funding_rate"
    INDEX_PRICE = "index_price"


class StorageType(Enum):
    SQLITE = "sqlite"
    POSTGRESQL = "postgresql"


class ParamBackend(Enum):
    MEMORY = "memory"
    REDIS = "redis"


class RateLimiter:
    pass


class RateLimiterSync:
    pass


class ConfigType(TypedDict):
    apiKey: str
    secret: str
    exchange_id: str
    sandbox: bool
    password: NotRequired[str]
