"""A small client for the FlowFabric API, signed in as a given tenant user.

The build runs against a running backend (the same one the Studio uses), so
every change goes through the API's own validation, storage tiers,
permissions and audit log — exactly as if the administrator made it.
"""

import time
import uuid
from datetime import timedelta

import httpx
from sqlalchemy import text

from app.core.auth import create_access_token
from tests.conftest import AsyncSessionLocal


class ApiError(RuntimeError):
    pass


async def user_identity(tenant_name: str, email: str) -> tuple[uuid.UUID, uuid.UUID, uuid.UUID | None]:
    """(tenant_id, user_id, vendor_id) for an account, looked up directly."""
    async with AsyncSessionLocal() as db:
        row = (
            await db.execute(
                text(
                    "SELECT t.id AS tid, u.id AS uid, u.vendor_id AS vid FROM users u JOIN tenants t ON t.id = u.tenant_id "
                    "WHERE t.name = :t AND lower(u.email) = lower(:e)"
                ),
                {"t": tenant_name, "e": email},
            )
        ).one_or_none()
    if row is None:
        raise ApiError(f"No account {email} in tenant {tenant_name}")
    return row.tid, row.uid, row.vid


class Api:
    """Sync JSON client; raises ApiError with the server's message on failure."""

    def __init__(self, base_url: str, tenant_id: uuid.UUID, user_id: uuid.UUID, vendor_id: uuid.UUID | None = None):
        # A short-lived token without a session — the build is one sitting.
        token = create_access_token(tenant_id=tenant_id, user_id=user_id, vendor_id=vendor_id, lifetime=timedelta(hours=2))
        self.http = httpx.Client(base_url=base_url, headers={"Authorization": f"Bearer {token}"}, timeout=120)

    def _call(self, method: str, path: str, **kw):
        for _ in range(30):
            resp = self.http.request(method, path, **kw)
            # A table change waits while a field index builds in the background.
            if resp.status_code == 409 and "try again" in resp.text:
                time.sleep(2)
                continue
            break
        if resp.status_code >= 400:
            try:
                detail = resp.json().get("detail")
            except Exception:
                detail = resp.text
            raise ApiError(f"{method} {path} -> {resp.status_code}: {detail}")
        return None if resp.status_code == 204 or not resp.content else resp.json()

    def get(self, path: str, **params):
        return self._call("GET", path, params=params or None)

    def post(self, path: str, body=None):
        return self._call("POST", path, json=body if body is not None else {})

    def patch(self, path: str, body):
        return self._call("PATCH", path, json=body)

    def put(self, path: str, body=None):
        return self._call("PUT", path, json=body if body is not None else {})

    def delete(self, path: str):
        return self._call("DELETE", path)
