import json
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Dict, Optional, Tuple


FALLBACK_MODELS = {"qwen3-vl-plus", "kimi-k2.5", "minimax-m2.5"}
STATE_PATH = Path(__file__).resolve().parents[1] / "data" / "aliyun_fallback_state.json"


def _read_state() -> Dict:
    if not STATE_PATH.exists():
        return {"models": {}}
    try:
        data = json.loads(STATE_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {"models": {}}
    if not isinstance(data, dict):
        return {"models": {}}
    if "models" not in data or not isinstance(data["models"], dict):
        data["models"] = {}
    return data


def _write_state(data: Dict) -> None:
    STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
    STATE_PATH.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def get_disabled_until(model_key: str) -> Optional[float]:
    data = _read_state()
    entry = (data.get("models") or {}).get(model_key) or {}
    ts = entry.get("disabled_until")
    try:
        return float(ts) if ts is not None else None
    except (TypeError, ValueError):
        return None


def is_fallback_active(model_key: str, now_ts: Optional[float] = None) -> bool:
    if model_key not in FALLBACK_MODELS:
        return False
    now_ts = float(now_ts) if now_ts is not None else datetime.now(tz=timezone.utc).timestamp()
    disabled_until = get_disabled_until(model_key)
    return bool(disabled_until and disabled_until > now_ts)


def set_disabled_until(model_key: str, disabled_until_ts: float, reason: str = "") -> None:
    if model_key not in FALLBACK_MODELS:
        return
    data = _read_state()
    models = data.setdefault("models", {})
    models[model_key] = {
        "disabled_until": float(disabled_until_ts),
        "reason": reason,
        "updated_at": datetime.now(tz=timezone.utc).timestamp(),
    }
    _write_state(data)


def _next_monday_utc8(now: datetime) -> datetime:
    # Monday = 0
    weekday = now.weekday()
    days_ahead = (7 - weekday) % 7
    if days_ahead == 0:
        days_ahead = 7
    target = (now + timedelta(days=days_ahead)).replace(hour=0, minute=0, second=0, microsecond=0)
    return target


def _next_month_same_day_utc8(now: datetime) -> datetime:
    year = now.year
    month = now.month + 1
    if month > 12:
        month = 1
        year += 1
    # clamp day to last day of next month
    if month == 12:
        next_month = datetime(year + 1, 1, 1, tzinfo=now.tzinfo)
    else:
        next_month = datetime(year, month + 1, 1, tzinfo=now.tzinfo)
    last_day = (next_month - timedelta(days=1)).day
    day = min(now.day, last_day)
    return datetime(year, month, day, 0, 0, 0, tzinfo=now.tzinfo)


def compute_disabled_until(error_text: str) -> Tuple[Optional[float], Optional[str]]:
    if not error_text:
        return None, None
    text = str(error_text).lower()
    tz8 = timezone(timedelta(hours=8))
    now = datetime.now(tz=tz8)

    if "hour allocated quota exceeded" in text or "每 5 小时请求额度已用完" in text:
        until = now + timedelta(hours=5)
        return until.astimezone(timezone.utc).timestamp(), "hour_quota"
    if "week allocated quota exceeded" in text or "每周请求额度已用完" in text:
        until = _next_monday_utc8(now)
        return until.astimezone(timezone.utc).timestamp(), "week_quota"
    if "month allocated quota exceeded" in text or "每月请求额度已用完" in text:
        until = _next_month_same_day_utc8(now)
        return until.astimezone(timezone.utc).timestamp(), "month_quota"
    return None, None
