"""
LEAF REST API
=============

All endpoints require a token via:
  - Authorization: Bearer <token>   (header)
  - ?token=<token>                  (query param)

Endpoints
---------
GET /api/managements
    List all managements the token owner has access to.

GET /api/data/recent?limit=20
    Most recent sensor readings across all accessible departments.

GET /api/data
    Sensor data with optional filters.
    Required: organization + department (text names)
    Optional filters:
      entity   str   filter to one entity
      metric   str   filter to one metric
      from     str   start time (ISO 8601, inclusive)
      to       str   end time   (ISO 8601, exclusive)
      limit    int   max rows   (default 1000, max 10000)
"""

import json
from typing import Optional

from fastapi import Depends, Query
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from starlette.requests import Request
from starlette.responses import JSONResponse
from nicegui import app

import db


# ---------------------------------------------------------------------------
# Swagger UI  (/api/docs)
# ---------------------------------------------------------------------------


@app.get("/api/docs", include_in_schema=False)
async def api_docs_ui():
    return get_swagger_ui_html(openapi_url="/api/openapi.json", title="LEAF REST API")


@app.get("/api/openapi.json", include_in_schema=False)
async def api_openapi_schema():
    api_routes = [
        r
        for r in app.routes
        if hasattr(r, "path")
        and r.path.startswith("/api/")
        and r.path not in ("/api/docs", "/api/openapi.json")
    ]
    return get_openapi(
        title="LEAF REST API",
        version="1.0.0",
        description=(
            "Token-authenticated API for accessing LEAF sensor data.\n\n"
            "Pass your token via **Authorization: Bearer \\<token\\>** header "
            "or **?token=\\<token\\>** query parameter.\n\n"
            "Generate tokens on the [API Tokens](/tokens) page."
        ),
        routes=api_routes,
    )


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_bearer = HTTPBearer(auto_error=False)


def _get_token(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer),
) -> Optional[str]:
    if credentials:
        return credentials.credentials or None
    return request.query_params.get("token") or None


def _unauthorized():
    return JSONResponse(
        {
            "error": "Unauthorized — provide a valid API token via Authorization: Bearer <token>."
        },
        status_code=401,
    )


def _serialize_rows(rows) -> list[dict]:
    out = []
    for r in rows:
        tags = r["tags"]
        if isinstance(tags, str):
            tags = json.loads(tags)
        out.append(
            {
                "time": r["time"].isoformat(),
                "entity": r["entity"],
                "metric": r["metric"],
                "value": r["value"],
                "tags": tags,
                "department_id": str(r["department_id"])
                if r["department_id"]
                else None,
                "organization_id": str(r["organization_id"])
                if r["organization_id"]
                else None,
            }
        )
    return out


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------


@app.get("/api/managements")
async def api_managements(token: Optional[str] = Depends(_get_token)):
    """List managements accessible to the token."""
    if not token:
        return _unauthorized()

    rows = await db.fetch("SELECT * FROM management_for_token($1)", token)
    if not rows:
        # Empty result = invalid/expired token or no access
        return _unauthorized()

    return JSONResponse(
        [
            {
                "id": str(r["id"]),
                "name": r["name"],
                "organization": r["organization_name"],
                "department": r["department_name"],
                "entity": r["entity"],
                "time_start": r["time_start"].isoformat() if r["time_start"] else None,
                "time_end": r["time_end"].isoformat() if r["time_end"] else None,
            }
            for r in rows
        ]
    )


@app.get("/api/data/recent")
async def api_data_recent(limit: int = 20, token: Optional[str] = Depends(_get_token)):
    """Most recent sensor readings across all accessible departments."""
    if not token:
        return _unauthorized()

    limit = max(1, min(limit, 1000))
    rows = await db.fetch(
        "SELECT * FROM sensor_data_recent_by_token($1, $2)",
        token,
        limit,
    )
    if rows is None:
        return _unauthorized()

    return JSONResponse(_serialize_rows(rows))


@app.get("/api/data")
async def api_data(
    organization: Optional[str] = None,
    department: Optional[str] = None,
    entity: Optional[str] = None,
    metric: Optional[str] = None,
    from_ts: Optional[str] = Query(default=None, alias="from"),
    to_ts: Optional[str] = Query(default=None, alias="to"),
    limit: int = 1000,
    token: Optional[str] = Depends(_get_token),
):
    """Sensor data with optional filters.

    Scope (required): organization + department (text names, e.g. ?organization=WUR&department=SSB)

    Additional filters: entity, metric, from_ts, to_ts (ISO 8601 timestamps).
    """
    try:
        if not token:
            return _unauthorized()
        limit = max(1, min(limit, 10000))

        if not (organization and department):
            return JSONResponse(
                {"error": "Provide scope: organization and department (text names)."},
                status_code=400,
            )

        base_params: list = [token, organization, department]
        fn_call = "sensor_data_for_department_by_token($1, $2, $3)"

        # Build WHERE clauses for optional filters
        extra_params: list = []
        clauses: list[str] = []
        i = len(base_params) + 1

        if entity:
            clauses.append(f"entity = ${i}")
            extra_params.append(entity)
            i += 1
        if metric:
            clauses.append(f"metric = ${i}")
            extra_params.append(metric)
            i += 1
        if from_ts:
            clauses.append(f'"time" >= ${i}::text::timestamptz')
            extra_params.append(from_ts)
            i += 1
        if to_ts:
            clauses.append(f'"time" < ${i}::text::timestamptz')
            extra_params.append(to_ts)
            i += 1

        where = ("WHERE " + " AND ".join(clauses)) if clauses else ""

        sql = f"""
            SELECT time, entity, metric, value, tags, department_id, organization_id
            FROM   {fn_call}
            {where}
            ORDER  BY time DESC
            LIMIT  {limit}
        """

        rows = await db.fetch(sql, *(base_params + extra_params))
        return JSONResponse(_serialize_rows(rows))

    except Exception:
        return JSONResponse({"error": "Internal server error."}, status_code=500)
