from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import httpx


@dataclass(frozen=True)
class ReleaseTestConfig:
    base_url: str
    admin_username: str
    admin_password: str
    timeout_seconds: float
    tenant_a_code: str
    tenant_a_name: str
    tenant_b_code: str
    tenant_b_name: str
    module_name: str
    module_path: str


class ReleaseApiClient:
    def __init__(self, config: ReleaseTestConfig, token: str | None = None) -> None:
        self.config = config
        self.token = token
        self._client = httpx.Client(base_url=config.base_url, timeout=config.timeout_seconds)

    def close(self) -> None:
        self._client.close()

    def with_token(self, token: str) -> "ReleaseApiClient":
        return ReleaseApiClient(self.config, token=token)

    def request(self, method: str, path: str, *, expected_status: int | None = 200, **kwargs: Any) -> httpx.Response:
        headers = dict(kwargs.pop("headers", {}) or {})
        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"
        response = self._client.request(method, path, headers=headers, **kwargs)
        if expected_status is not None:
            assert response.status_code == expected_status, self._format_error(response)
        return response

    def get(self, path: str, **kwargs: Any) -> httpx.Response:
        return self.request("GET", path, **kwargs)

    def post(self, path: str, **kwargs: Any) -> httpx.Response:
        return self.request("POST", path, **kwargs)

    def put(self, path: str, **kwargs: Any) -> httpx.Response:
        return self.request("PUT", path, **kwargs)

    def patch(self, path: str, **kwargs: Any) -> httpx.Response:
        return self.request("PATCH", path, **kwargs)

    def delete(self, path: str, **kwargs: Any) -> httpx.Response:
        return self.request("DELETE", path, **kwargs)

    def login_password(self, username: str, password: str) -> dict[str, Any]:
        response = self.post(
            "/api/auth/login",
            json={"username": username, "password": password},
            expected_status=200,
        )
        payload = response.json()
        assert payload.get("success") is True, payload
        data = payload.get("data") or {}
        assert data.get("access_token"), payload
        return data

    def login_oauth(
        self,
        *,
        sub: str,
        username: str,
        nickname: str,
        ou_id: str,
        ou_name: str,
        area: str | None = None,
    ) -> dict[str, Any]:
        response = self.post(
            "/api/auth/login",
            json={
                "userInfo": {
                    "sub": sub,
                    "username": username,
                    "nickname": nickname,
                    "email": f"{username}@pytest.local",
                    "phone_number": "13800000000",
                    "ou_id": ou_id,
                    "ou_name": ou_name,
                    "is_leader": False,
                },
                "area": area,
                "expiresIn": 3600,
            },
            expected_status=200,
        )
        payload = response.json()
        assert payload.get("success") is True, payload
        data = payload.get("data") or {}
        assert data.get("access_token"), payload
        return data

    @staticmethod
    def json_data(response: httpx.Response) -> Any:
        payload = response.json()
        if isinstance(payload, dict) and "data" in payload:
            return payload["data"]
        return payload

    @staticmethod
    def _format_error(response: httpx.Response) -> str:
        try:
            body = response.json()
        except Exception:
            body = response.text
        return f"{response.request.method} {response.request.url} -> {response.status_code}: {body}"


def flatten_route_paths(routes: list[dict[str, Any]]) -> set[str]:
    paths: set[str] = set()

    def collect(items: list[dict[str, Any]]) -> None:
        for item in items:
            route_path = str(item.get("route_path") or "")
            if route_path:
                paths.add(route_path)
            children = item.get("children") or []
            if isinstance(children, list):
                collect(children)

    collect(routes)
    return paths
