import json
from datetime import datetime, timezone
from pydantic import BaseModel, Field
from enum import Enum
from typing import Optional, List, Any, Dict

class BizException(Exception):
    def __init__(
        self,
        code: int,
        message: str,
        http_status: int = 400,
        extra: Optional[Dict[str, Any]] = None,
    ):
        super().__init__(message)
        self.code = code
        self.message = message
        self.http_status = http_status
        self.extra = extra
        
    def __str__(self):
        return (
        f"{self.__class__.__name__}("
        f"code={self.code}, "
        f"http_status={self.http_status}, "
        f"message='{self.message}', "
        f"extra={self.extra})"
    )

    def __repr__(self):
        return self.__str__()


class NotFoundError(BizException):
    def __init__(self, message="Resource not found"):
        super().__init__(code=40401, message=message, http_status=404)

class RateLimiteddError(BizException):
    def __init__(self, message="Rate limited"):
        super().__init__(code=42901, message=message, http_status=429)
        
class SessionExpiredOrInvalidError(BizException):
    def __init__(self, message="Session expired or invalid"):
        super().__init__(code=40101, message=message, http_status=401)

class PermissionDeniedError(BizException):
    def __init__(self, message="Permission denied"):
        super().__init__(code=40301, message=message, http_status=403)

class BizLogicError(BizException):
    def __init__(self, message="Business logic error"):
        super().__init__(code=40001, message=message)

class AvailabilityStatus(str, Enum):
    NoneAvailable = "NoAvailable"
    Available = "Available"
    Waitlist = "Waitlist"

class QueryWaitMode(str, Enum):
    Loop = "Loop"
    Fixed = "Fixed"
    Random = "Random"

class QueryWaitConfig(BaseModel):
    mode: QueryWaitMode = QueryWaitMode.Loop
    fixed_wait: int = 0
    random_min: int = 0
    random_max: int = 0

class PluginConfig(BaseModel):
    lib_path: str = Field(default="plugins")
    plugin_name: str = Field(default="")
    plugin_bin: str = Field(default="")
    plugin_proto: str = Field(default="IVSPlg")

class AppointmentType(BaseModel):
    weight: int = 0
    routing_key: str = ''
    country: str = ''
    city: str = ''
    visa_type: str = ''

# === 哨兵配置 ===
class SentinelConfig(BaseModel):
    account_source: str = "built-in"
    account_pool_id: str = ""
    target_instances: int = 1
    account_cd: int = 180*60        # 单位 秒
    signal_ttl: int = 180           # 单位 秒
    query_wait: QueryWaitConfig = Field(default_factory=QueryWaitConfig)

# === Booker配置 ===
class BookerConfig(BaseModel):
    account_source: str = "built-in" # "built-in" 或 "order"
    account_pool_id: str = ""        # 仅在 built-in 模式下使用 账号池名字
    account_cd: int = 180*60         # 仅在 built-in 模式下使用 账号冷却时间 单位 秒
    target_instances: int = 1        # built-in下为全局限制; order下为单队列限制
    keep_alive: int = 60             # 每60秒保活一次
    booking_cooldown: float = 10.0   # 单位 秒
    max_bookings_per_account: int = 1

class GroupConfig(BaseModel):
    identifier: str = ""
    debug: bool = False
    enable: bool = False
    need_account: bool = False
    need_proxy: bool = False
    proxy_pool: List[str] = Field(default_factory=list)
    proxy_cd: int = 5*60                # 单位 秒
    active_time_start: str = "00:00"    # 默认执行起始时间
    active_time_end: str = "23:59"      # 默认执行结束时间
    session_max_life: int = 30*60       # 单位 秒
    login_backoff: int = 1800           # 创建会话Rate limited 后账号自动退避 秒
    sentinel: SentinelConfig = Field(default_factory=SentinelConfig)
    booker: BookerConfig = Field(default_factory=BookerConfig)
    plugin_config: PluginConfig = Field(default_factory=PluginConfig)
    appointment_types: List[AppointmentType] = Field(default_factory=list)
    website: str = ""
    free_config: Dict[str, Any] = Field(default_factory=dict)

    @classmethod
    def from_json(cls, data: Dict[str, Any]) -> "GroupConfig":
        return cls.model_validate(data)

class VSAccount(BaseModel):
    id: int = 0
    username: str = ""
    password: str = ""

class VSProxy(BaseModel):
    id: int = 0
    proto: str = "http"
    ip: str = ""
    port: int = 0
    username: str = ""
    password: str = ""
    
class BookerStandaloneConfig(BaseModel):
    identifier: str = ""
    enable: bool = True
    debug: bool = False
    account: VSAccount = Field(default_factory=VSAccount)
    proxy_pool: List[str] = Field(default_factory=list)
    active_time_start: str = "00:00"    # 默认执行起始时间
    active_time_end: str = "23:59"      # 默认执行结束时间
    session_max_life: int = 30*60       # 单位 秒
    query_wait: QueryWaitConfig = Field(default_factory=QueryWaitConfig)
    login_interval: int = 1800          # 登录间隔
    plugin_config: PluginConfig = Field(default_factory=PluginConfig)
    appointment_types: List[AppointmentType] = Field(default_factory=list)
    user_preferences: Dict[str, Any] = Field(default_factory=dict)
    free_config: Dict[str, Any] = Field(default_factory=dict)

    @classmethod
    def from_json(cls, data: Dict[str, Any]) -> "BookerStandaloneConfig":
        return cls.model_validate(data)

class VSPlgConfig(BaseModel):
    debug: bool = False
    account: VSAccount = Field(default_factory=VSAccount)
    proxy: VSProxy = Field(default_factory=VSProxy)
    free_config: dict = Field(default_factory=dict)
    session_max_life: int = 30*60       # 单位 秒

class TimeSlot(BaseModel):
    time: str = ""     # "hh:mm"
    label: str = ""

class DateAvailability(BaseModel):
    date: Optional[datetime] = None
    times: List[TimeSlot] = Field(default_factory=list)

class VSQueryResult(BaseModel):
    success: bool = False
    apt_type: AppointmentType = Field(default_factory=AppointmentType)
    availability_status: AvailabilityStatus = AvailabilityStatus.NoneAvailable
    earliest_date: Optional[datetime] = None
    availability: List[DateAvailability] = Field(default_factory=list)
    
    model_config = {"validate_assignment": True}
    
    def to_snapshot_payload(self) -> dict:
        def format_avail_item(item: DateAvailability) -> dict:
            data = {}
            if item.date:
                data["date"] = item.date.strftime("%Y-%m-%d")
            else:
                data["date"] = None
            if item.times:
                data["times"] = [t.model_dump() for t in item.times]
            return data
        
        return {
            "routing_key": self.apt_type.routing_key,
            "country": self.apt_type.country,
            "city": self.apt_type.city,
            "visa_type": self.apt_type.visa_type,
            "availability_status": self.availability_status.value,
            "earliest_date": self.earliest_date.strftime("%Y-%m-%d") if self.earliest_date else None,
            "availability": [format_avail_item(a) for a in self.availability],
            "snapshot_source": "worker",
            "snapshot_at": datetime.now(timezone.utc).isoformat()
        }
   
class VSBookResult(BaseModel):
    success: bool = False
    session_id: str = ""
    urn: str = ""
    account: str = ""
    label: str = ""
    book_date: str = ""
    book_time: str = ""
    payment_link: str = ""

# === 内部任务对象 ===
class Task(BaseModel):
    instance: Any              
    qw_cfg: Optional[QueryWaitConfig] = None,
    next_run: float = 0.0      
    book_allowed: bool = True
    # 订单模式下，保存绑定的 Task ID
    task_ref: Optional[int] = None
    # 允许预订的 routing_key 列表
    acceptable_routing_keys: List[str] = Field(default_factory=list)
    # 来源标识(用于配额统计)
    source_queue: str = ""
    successful_bookings: int = 0
    # 下一次允许心跳时间
    next_remote_ping: float = 0.0
    
    model_config = {
        "underscore_attrs_are_private": True,
        "arbitrary_types_allowed": True
    }
