# retoor <retoor@molodetz.nl>

import json
import requests
from starlette.requests import Request
from tests.conftest import BASE_URL, run_async
from devplacepy.database import get_table, set_setting
from devplacepy.utils import generate_uid
import devplacepy.services.openai_gateway.gateway as gwmod
from devplacepy.services.openai_gateway import GatewayService
class FakeResp_openai_gateway:
    def __init__(self, status=200, payload=None, ctype="application/json", content=b""):
        self.status_code = status
        self._payload = payload
        self.text = json.dumps(payload) if payload is not None else ""
        self.headers = {"content-type": ctype}
        self.content = content

    def json(self):
        if self._payload is None:
            raise ValueError("no json")
        return self._payload
class FakeRequest:
    def __init__(self, method, url, json_body):
        self.method = method
        self.url = url
        self.json_body = json_body
        self.extensions = {}
class FakeClient_openai_gateway:
    def __init__(self, *a, **k):
        self.calls = []

    def build_request(self, method, url, headers=None, json=None, content=None):
        return FakeRequest(method, url, json)

    async def send(self, request):
        self.calls.append((request.url, request.json_body))
        body = request.json_body or {}
        return FakeResp_openai_gateway(
            payload={
                "id": "x",
                "model": body.get("model"),
                "choices": [{"message": {"content": "hi there"}}],
            }
        )

    async def post(self, url, headers=None, json=None, timeout=None):
        self.calls.append((url, json))
        return FakeResp_openai_gateway(
            payload={
                "id": "x",
                "model": json.get("model"),
                "choices": [{"message": {"content": "hi there"}}],
            }
        )

    async def request(self, method, url, headers=None, content=None):
        return FakeResp_openai_gateway(payload={"ok": True})

    async def aclose(self):
        pass
def _make_request_openai_gateway(headers=None, cookies=None):
    headers = headers or {}
    raw = [(k.lower().encode(), v.encode()) for k, v in headers.items()]
    if cookies:
        raw.append(
            (b"cookie", "; ".join(f"{k}={v}" for k, v in cookies.items()).encode())
        )
    return Request(
        {
            "type": "http",
            "method": "POST",
            "path": "/openai/v1/chat/completions",
            "query_string": b"",
            "headers": raw,
            "state": {},
        }
    )
def _make_admin_openai_gateway(role="Admin"):
    username = f"gw_{generate_uid()[:8]}"
    api_key = generate_uid()
    get_table("users").insert(
        {
            "uid": generate_uid(),
            "username": username,
            "email": f"{username}@t.dev",
            "api_key": api_key,
            "role": role,
            "is_active": True,
        }
    )
    return username, api_key
def _config(page, **fields):
    page.request.post(f"{BASE_URL}/admin/services/openai/config", form=fields)


def test_model_is_forced(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    cfg["gateway_force_model"] = True
    cfg["gateway_model"] = "deepseek-chat"
    rt = svc.runtime()
    run_async(
        rt.handle_chat(
            {"model": "gpt-4", "messages": [{"role": "user", "content": "hi"}]},
            cfg,
            ("guest", "test"),
            "test",
        )
    )
    assert rt._client.calls[-1][1]["model"] == "deepseek-chat"


def test_vision_rewrites_image_to_text(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    cfg["gateway_vision_enabled"] = True
    cfg["gateway_vision_key"] = ""  # no key -> placeholder text, still rewrites to str
    rt = svc.runtime()
    msgs = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "what is this"},
                {"type": "image_url", "image_url": {"url": "http://x/y.png"}},
            ],
        }
    ]
    run_async(rt.handle_chat({"messages": msgs}, cfg, ("guest", "test"), "test"))
    sent = rt._client.calls[-1][1]["messages"][0]["content"]
    assert isinstance(sent, str) and "vision" in sent.lower()


def test_streaming_emits_sse(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    rt = svc.runtime()
    resp = run_async(
        rt.handle_chat(
            {"messages": [{"role": "user", "content": "hi"}], "stream": True},
            cfg,
            ("guest", "test"),
            "test",
        )
    )

    async def drain():
        out = []
        async for chunk in resp.body_iterator:
            out.append(chunk if isinstance(chunk, str) else chunk.decode())
        return "".join(out)

    body = run_async(drain())
    assert "chat.completion.chunk" in body
    assert "[DONE]" in body


def test_authorize_static_access_key(local_db):
    set_setting("gateway_require_auth", "1")
    set_setting("gateway_access_key", "topsecret")
    try:
        svc = GatewayService()
        assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": "topsecret"})) is True
        assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": "wrong"})) is False
    finally:
        set_setting("gateway_access_key", "")
        set_setting("gateway_require_auth", "1")


def test_authorize_admin_and_user_toggles(local_db):
    set_setting("gateway_require_auth", "1")
    set_setting("gateway_access_key", "")
    set_setting("gateway_allow_admins", "1")
    set_setting("gateway_allow_users", "0")
    try:
        _, admin_key = _make_admin_openai_gateway(role="Admin")
        _, member_key = _make_admin_openai_gateway(role="Member")
        svc = GatewayService()

        assert (
            svc.authorize(_make_request_openai_gateway(headers={"Authorization": f"Bearer {admin_key}"}))
            is True
        )
        assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": member_key})) is False

        set_setting("gateway_allow_users", "1")
        assert svc.authorize(_make_request_openai_gateway(headers={"X-API-KEY": member_key})) is True
    finally:
        set_setting("gateway_allow_admins", "1")
        set_setting("gateway_allow_users", "1")
        set_setting("gateway_require_auth", "1")
        set_setting("gateway_access_key", "")


def test_authorize_require_auth_off_is_open(local_db):
    set_setting("gateway_require_auth", "0")
    try:
        svc = GatewayService()
        assert svc.authorize(_make_request_openai_gateway()) is True
    finally:
        set_setting("gateway_require_auth", "1")


class FakeEmbedClient_openai_gateway:
    def __init__(self, *a, **k):
        self.calls = []

    def build_request(self, method, url, headers=None, json=None, content=None):
        return FakeRequest(method, url, json)

    async def send(self, request):
        self.calls.append((request.url, request.json_body))
        body = request.json_body or {}
        return FakeResp_openai_gateway(
            payload={
                "object": "list",
                "model": body.get("model"),
                "data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2]}],
                "usage": {"prompt_tokens": 5, "total_tokens": 5},
            }
        )

    async def aclose(self):
        pass


def test_embeddings_remaps_alias_to_configured_model(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    cfg["gateway_force_model"] = False
    cfg["gateway_embed_enabled"] = True
    cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
    rt = svc.runtime()
    run_async(
        rt.handle_embeddings(
            {"model": "molodetz~embed", "input": "hello"},
            cfg,
            ("guest", "test"),
            "test",
        )
    )
    assert rt._client.calls[-1][1]["model"] == "qwen/qwen3-embedding-8b"


def test_embeddings_success_records_ledger(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    cfg["gateway_force_model"] = True
    cfg["gateway_embed_enabled"] = True
    cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
    rt = svc.runtime()
    before = rt.embed_calls
    resp = run_async(
        rt.handle_embeddings(
            {"model": "molodetz~embed", "input": "hello"},
            cfg,
            ("guest", "ledger_probe"),
            "test",
        )
    )
    assert resp.status_code == 200
    payload = json.loads(bytes(resp.body).decode())
    assert payload["data"][0]["embedding"] == [0.1, 0.2]
    assert rt.embed_calls == before + 1
    row = get_table("gateway_usage_ledger").find_one(owner_id="ledger_probe")
    assert row is not None
    assert row["backend"] == "embed"
    assert row["endpoint"] == "embeddings"
    assert row["requested_model"] == "molodetz~embed"
    assert row["model"] == "qwen/qwen3-embedding-8b"
    assert row["success"] == 1


def test_embeddings_disabled_returns_503(local_db, monkeypatch):
    monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
    svc = GatewayService()
    cfg = svc.effective_config()
    cfg["gateway_embed_enabled"] = False
    rt = svc.runtime()
    resp = run_async(
        rt.handle_embeddings(
            {"input": "hello"}, cfg, ("guest", "test"), "test"
        )
    )
    assert resp.status_code == 503


def test_compute_cost_embed_branch():
    from devplacepy.services.openai_gateway.usage import (
        Pricing,
        compute_cost,
        normalize_usage,
    )

    pricing = Pricing(
        chat_cache_hit_per_m=0.0,
        chat_cache_miss_per_m=0.0,
        chat_output_per_m=0.0,
        vision_input_per_m=0.0,
        vision_output_per_m=0.0,
        embed_input_per_m=0.01,
    )
    usage = {"prompt_tokens": 1_000_000, "total_tokens": 1_000_000}
    norm = normalize_usage(usage)
    total, input_cost, output_cost, native = compute_cost(
        usage, norm, pricing, "embed"
    )
    assert native is False
    assert output_cost == 0.0
    assert abs(total - 0.01) < 1e-9
    assert abs(input_cost - 0.01) < 1e-9
