"""Server-side enhancement + prompts: config gating, endpoints, key safety."""
from __future__ import annotations

import pytest

from app import enhance as enhance_mod
from app import prompts as prompts_mod
from app.config import ServerConfig
from app.main import create_app


class _Cfg:
    """Minimal config-like object for enhance helper tests."""
    def __init__(self, provider="", key="", model=""):
        self.llm_provider = provider
        self.llm_api_key = key
        self.llm_model = model


# ── availability / provider gating ──────────────────────────────────────────

def test_is_available_requires_provider_and_key():
    assert not enhance_mod.is_available(_Cfg())
    assert not enhance_mod.is_available(_Cfg(provider="anthropic"))
    assert not enhance_mod.is_available(_Cfg(key="sk-x"))
    assert enhance_mod.is_available(_Cfg(provider="anthropic", key="sk-x"))


def test_provider_of_normalizes():
    assert enhance_mod.provider_of(_Cfg(provider="ANTHROPIC")) == "anthropic"
    assert enhance_mod.provider_of(_Cfg(provider="bogus")) == ""


@pytest.mark.asyncio
async def test_enhance_unconfigured_raises():
    with pytest.raises(enhance_mod.EnhanceError):
        await enhance_mod.enhance(_Cfg(), text="hi", system_prompt="clean")


class _FakeResp:
    def __init__(self, payload, status=200):
        self._payload = payload
        self.status_code = status
        self.text = str(payload)

    def json(self):
        return self._payload

    def raise_for_status(self):
        if self.status_code >= 400:
            import httpx
            raise httpx.HTTPStatusError("err", request=None, response=self)


class _FakeClient:
    """Stand-in for httpx.AsyncClient capturing the posted body."""
    posted = {}

    def __init__(self, *a, **k):
        pass

    async def __aenter__(self):
        return self

    async def __aexit__(self, *e):
        return False

    async def post(self, url, headers=None, json=None):
        _FakeClient.posted = {"url": url, "headers": headers, "json": json}
        if "anthropic" in url:
            return _FakeResp({"stop_reason": "end_turn",
                              "content": [{"type": "text", "text": "CLEANED"}]})
        return _FakeResp({"choices": [{"finish_reason": "stop",
                          "message": {"content": "CLEANED"}}]})


@pytest.mark.asyncio
async def test_enhance_anthropic_path(monkeypatch):
    monkeypatch.setattr(enhance_mod.httpx, "AsyncClient", _FakeClient)
    out = await enhance_mod.enhance(
        _Cfg(provider="anthropic", key="sk-secret"),
        text="raw text", system_prompt="clean it")
    assert out == "CLEANED"
    # key goes in the header, system prompt in the body, never swapped
    assert _FakeClient.posted["headers"]["x-api-key"] == "sk-secret"
    assert _FakeClient.posted["json"]["system"] == "clean it"


@pytest.mark.asyncio
async def test_enhance_openai_path(monkeypatch):
    monkeypatch.setattr(enhance_mod.httpx, "AsyncClient", _FakeClient)
    out = await enhance_mod.enhance(
        _Cfg(provider="openai", key="sk-o"),
        text="raw", system_prompt="clean")
    assert out == "CLEANED"
    assert _FakeClient.posted["headers"]["Authorization"] == "Bearer sk-o"


# ── /v1/status advertises enhance without leaking the key ───────────────────

def _client(cfg):
    from fastapi.testclient import TestClient
    from conftest import FakeEngine
    app = create_app(cfg, engine=FakeEngine())
    return TestClient(app)


def test_status_enhance_off_by_default(auth_headers):
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1")
    with _client(cfg) as c:
        data = c.get("/v1/status", headers=auth_headers).json()
    assert data["enhance"] == {"available": False, "provider": ""}


def test_status_enhance_on_but_no_key_leak(auth_headers):
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1",
                       llm_provider="anthropic", llm_api_key="sk-secret-123")
    with _client(cfg) as c:
        resp = c.get("/v1/status", headers=auth_headers)
    assert resp.json()["enhance"] == {"available": True, "provider": "anthropic"}
    assert "sk-secret-123" not in resp.text


def test_enhance_endpoint_501_when_unconfigured(auth_headers):
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1")
    with _client(cfg) as c:
        r = c.post("/v1/enhance", headers=auth_headers,
                   json={"text": "hi", "system_prompt": "clean"})
    assert r.status_code == 501


def test_enhance_requires_auth():
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1",
                       llm_provider="anthropic", llm_api_key="sk-x")
    with _client(cfg) as c:
        r = c.post("/v1/enhance", json={"text": "hi", "system_prompt": "x"})
    assert r.status_code == 401


def test_enhance_rejects_oversized(auth_headers):
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1",
                       llm_provider="anthropic", llm_api_key="sk-x")
    with _client(cfg) as c:
        r = c.post("/v1/enhance", headers=auth_headers,
                   json={"text": "x" * 200_001, "system_prompt": "clean"})
    assert r.status_code == 422


# ── /v1/prompts + prompt storage ────────────────────────────────────────────

def test_prompts_crud(tmp_path):
    pm = prompts_mod
    assert pm.load_prompts(tmp_path) == []
    e = pm.add_prompt(tmp_path, "Formal E-Mail", "Rewrite as a formal e-mail.")
    assert e["id"] == "formal-e-mail"
    rows = pm.load_prompts(tmp_path)
    assert rows == [{"id": "formal-e-mail", "name": "Formal E-Mail",
                     "prompt": "Rewrite as a formal e-mail."}]
    # update by id
    pm.add_prompt(tmp_path, "Formal E-Mail", "Updated.", id="formal-e-mail")
    assert pm.load_prompts(tmp_path)[0]["prompt"] == "Updated."
    assert pm.remove_prompt(tmp_path, "formal-e-mail") is True
    assert pm.load_prompts(tmp_path) == []
    assert pm.remove_prompt(tmp_path, "nope") is False


def test_prompts_rejects_empty(tmp_path):
    with pytest.raises(ValueError):
        prompts_mod.add_prompt(tmp_path, "", "text")
    with pytest.raises(ValueError):
        prompts_mod.add_prompt(tmp_path, "name", "")


def test_prompts_ignores_corrupt_file(tmp_path):
    prompts_mod.prompts_path(tmp_path).write_text("not json", encoding="utf-8")
    assert prompts_mod.load_prompts(tmp_path) == []


def test_prompts_endpoint(auth_headers, tmp_path, monkeypatch):
    monkeypatch.setenv("VOXCAST_DATA_DIR", str(tmp_path))
    prompts_mod.add_prompt(tmp_path, "Notes", "Turn into notes.")
    cfg = ServerConfig(api_key="testkey", host="127.0.0.1")
    with _client(cfg) as c:
        r = c.get("/v1/prompts", headers=auth_headers)
    assert r.status_code == 200
    assert r.json()["prompts"][0]["name"] == "Notes"
