from __future__ import annotations

import json
from dataclasses import asdict, dataclass

from .json_types import JSONObject, JSONScalar


INVALID_TOKEN_MARKERS = (
    "invalid token",
    "token invalid",
    "token expired",
    "expired token",
    "token失效",
    "无效token",
    "登录失效",
    "登录过期",
    "会话过期",
    "session expired",
    "session invalid",
    "login token invalid",
    "access token invalid",
    "not logged in",
    "not login",
    "please login",
    "please log in",
    "未登录",
    "请登录",
    "重新登录",
)


@dataclass(slots=True)
class QingflowApiError(Exception):
    category: str
    message: str
    backend_code: JSONScalar = None
    request_id: str | None = None
    http_status: int | None = None
    details: JSONObject | None = None

    def to_dict(self) -> JSONObject:
        return asdict(self)

    def as_json(self) -> str:
        return json.dumps(self.to_dict(), ensure_ascii=False)

    def __str__(self) -> str:
        return self.as_json()

    def looks_like_invalid_token(self) -> bool:
        return message_looks_like_invalid_token(self.message)

    @classmethod
    def auth_required(cls, profile: str) -> "QingflowApiError":
        return cls(
            category="auth",
            message=f"Profile '{profile}' is not logged in. Run auth login or auth_use_credential first.",
        )

    @classmethod
    def workspace_not_selected(cls, profile: str) -> "QingflowApiError":
        return cls(
            category="workspace",
            message=f"WORKSPACE_NOT_SELECTED: profile '{profile}' has no workspace from auth context. Re-run auth login or auth_use_credential.",
        )

    @classmethod
    def config_error(cls, message: str, *, details: JSONObject | None = None) -> "QingflowApiError":
        return cls(category="config", message=message, details=details)

    @classmethod
    def not_supported(cls, message: str) -> "QingflowApiError":
        return cls(category="not_supported", message=message)


def raise_tool_error(error: QingflowApiError) -> None:
    raise RuntimeError(error.as_json())


def backend_code_value_int(code: JSONScalar) -> int | None:
    if isinstance(code, bool) or code is None:
        return None
    if isinstance(code, int):
        return code
    if isinstance(code, str):
        text = code.strip()
        if text:
            try:
                return int(text)
            except ValueError:
                return None
    return None


def backend_code_int(error: QingflowApiError) -> int | None:
    return backend_code_value_int(error.backend_code)


def message_looks_like_invalid_token(message: object) -> bool:
    text = str(message or "").lower()
    return any(marker in text for marker in INVALID_TOKEN_MARKERS)


def is_auth_like_error(error: QingflowApiError) -> bool:
    category = str(error.category or "").strip().lower()
    return category == "auth" or error.http_status == 401 or error.looks_like_invalid_token()
