"""FastAPI parity with Django: X-Mizan-Invalidate header, origin cache, token auth."""

from __future__ import annotations

import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel

from mizan_core.auth import AuthConfig, JWTConfig, create_access_token
from mizan_core.cache.backend import MemoryCache
from mizan_core.client.function import client
from mizan_core.dispatch import CacheOrchestrator
from mizan_core.registry import clear_registry, register
from mizan_fastapi import (
    MizanAuthMiddleware,
    MizanConfig,
    MizanError,
    mizan_auth,
    mizan_exception_handler,
    router as mizan_router,
)


class Out(BaseModel):
    ok: bool


SECRET = "x" * 32
JWT = JWTConfig(private_key=SECRET, public_key=SECRET)


def _app(*, with_cache=False, with_auth_dep=False) -> FastAPI:
    clear_registry()

    UserCtx = "user"

    @client(context=UserCtx)
    def user_profile(request, user_id: int) -> Out:
        return Out(ok=True)

    @client(affects=UserCtx)
    def update_profile(request, user_id: int) -> Out:
        return Out(ok=True)

    @client(auth=True)
    def whoami(request) -> Out:
        return Out(ok=True)

    register(user_profile, "user_profile")
    register(update_profile, "update_profile")
    register(whoami, "whoami")

    app = FastAPI()
    cache = CacheOrchestrator(MemoryCache(), SECRET) if with_cache else CacheOrchestrator(None, None)
    app.state.mizan_config = MizanConfig(auth=AuthConfig(jwt=JWT), cache=cache)
    deps = [Depends(mizan_auth())] if with_auth_dep else []
    app.include_router(mizan_router, prefix="/api/mizan", dependencies=deps)
    app.add_exception_handler(MizanError, mizan_exception_handler)
    return app


def test_mutation_emits_invalidate_header():
    c = TestClient(_app())
    r = c.post("/api/mizan/call/", json={"fn": "update_profile", "args": {"user_id": 5}})
    assert r.status_code == 200
    assert r.json()["invalidate"] == [{"context": "user", "params": {"user_id": 5}}]
    assert r.headers["X-Mizan-Invalidate"] == "user;user_id=5"


def test_origin_cache_hit_miss():
    c = TestClient(_app(with_cache=True))
    r1 = c.get("/api/mizan/ctx/user/", params={"user_id": 5})
    assert r1.status_code == 200 and r1.headers["X-Mizan-Cache"] == "MISS"
    r2 = c.get("/api/mizan/ctx/user/", params={"user_id": 5})
    assert r2.headers["X-Mizan-Cache"] == "HIT"
    assert r1.content == r2.content


def test_auth_required_rejects_anonymous():
    c = TestClient(_app())
    r = c.post("/api/mizan/call/", json={"fn": "whoami", "args": {}})
    assert r.status_code == 401


def test_auth_required_passes_with_bearer_jwt():
    c = TestClient(_app(with_auth_dep=True))
    tok = create_access_token("7", "sess", JWT, is_staff=True)
    r = c.post("/api/mizan/call/", json={"fn": "whoami", "args": {}},
              headers={"Authorization": f"Bearer {tok}"})
    assert r.status_code == 200 and r.json()["result"] == {"ok": True}


def test_invalid_bearer_token_rejected():
    c = TestClient(_app())
    r = c.post("/api/mizan/call/", json={"fn": "update_profile", "args": {"user_id": 1}},
              headers={"Authorization": "Bearer not-a-real-token"})
    assert r.status_code == 401
