"""モデル検査のフェイル クローズ。

「検査不能なら素通し」は、Content-Type を変える・本文を膨らませる・旧来のパスを使う
のいずれかで制限を無効化できてしまう。検査不能は拒否とする。
"""

from __future__ import annotations

import json

import pytest

from .conftest import auth, create_key

ALLOWED = "qwen3-8b"
FORBIDDEN = "gemma3-27b"


async def test_allowed_model_passes(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    async with client:
        response = await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "content-type": "application/json"},
            content=json.dumps({"model": ALLOWED, "messages": []}).encode(),
        )
    assert response.status_code == 200
    assert len(recorder.requests) == 1


async def test_forbidden_model_is_rejected(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    async with client:
        response = await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "content-type": "application/json"},
            content=json.dumps({"model": FORBIDDEN, "messages": []}).encode(),
        )
    assert response.status_code == 403
    assert response.json()["error"]["code"] == "model_not_allowed"
    assert recorder.requests == [], "拒否したのに上流へ転送しています"


# ---------------------------------------------------------------- 迂回経路 1


@pytest.mark.parametrize(
    ("content_type", "body"),
    [
        ("application/x-www-form-urlencoded", b"model=qwen3-8b&prompt=hi"),
        ("text/plain", b"not json at all"),
        ("application/octet-stream", b"\x00\x01\x02"),
        ("", b"model: qwen3-8b"),
    ],
)
async def test_bypass_by_content_type_is_blocked(
    db, keys, settings, build_proxy, recorder, content_type, body
):
    """Content-Type を変えて JSON 解釈を外しても素通しにならない。"""

    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    headers = {**auth(secret)}
    if content_type:
        headers["content-type"] = content_type
    async with client:
        response = await client.post("/v1/chat/completions", headers=headers, content=body)

    assert response.status_code == 403
    assert response.json()["error"]["code"] == "model_check_failed"
    assert recorder.requests == []


# ---------------------------------------------------------------- 迂回経路 2


async def test_bypass_by_oversized_body_is_blocked(db, keys, settings, build_proxy, recorder):
    """本文を膨らませても検査を省略しない。"""

    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    padding = "x" * (512 * 1024)
    body = json.dumps({"model": FORBIDDEN, "messages": [{"role": "user", "content": padding}]}).encode()
    async with client:
        response = await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "content-type": "application/json"},
            content=body,
        )

    assert response.status_code == 403
    assert recorder.requests == []


async def test_bypass_by_broken_json_padding_is_blocked(db, keys, settings, build_proxy, recorder):
    """巨大かつ JSON として壊れた本文も拒否する。"""

    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    body = json.dumps({"model": ALLOWED}).encode() + b" " + b"x" * (256 * 1024)
    async with client:
        response = await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "content-type": "application/json"},
            content=body,
        )

    assert response.status_code == 403
    assert recorder.requests == []


# ---------------------------------------------------------------- 迂回経路 3


@pytest.mark.parametrize(
    "path",
    [
        "/completion",
        "/completions",
        "/infill",
        "/v1/infill",
        "/v1/completions",
        "/v1/embeddings",
        "/v1/rerank",
        "/v1/reranking",
        "/apply-template",
        "/completion/",
        "/COMPLETION",
    ],
)
async def test_legacy_paths_are_checked(db, keys, settings, build_proxy, recorder, path):
    """旧来の生成パスを検査対象から漏らすと、そこが迂回路になる。"""

    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    async with client:
        response = await client.post(
            path,
            headers={**auth(secret), "content-type": "application/json"},
            content=json.dumps({"model": FORBIDDEN}).encode(),
        )

    assert response.status_code == 403, path
    assert recorder.requests == [], path


async def test_path_traversal_does_not_skip_model_check(db, keys, settings, build_proxy, recorder):
    """正規化して判定するため、``..`` を挟んでも検査対象から外れない。"""

    secret = await create_key(db, keys, settings, models=[ALLOWED])
    client, _ = build_proxy()
    async with client:
        response = await client.post(
            "/v1/embeddings/../completions",
            headers={**auth(secret), "content-type": "application/json"},
            content=json.dumps({"model": FORBIDDEN}).encode(),
        )

    assert response.status_code == 403
    assert recorder.requests == []


# ------------------------------------------------------------------ 無制限キー


async def test_unrestricted_key_skips_the_check(db, keys, settings, build_proxy, recorder):
    """許可リストが空なら無制限キーとして検査を省略する。"""

    secret = await create_key(db, keys, settings, models=[])
    client, _ = build_proxy()
    async with client:
        response = await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "content-type": "text/plain"},
            content=b"not json",
        )

    assert response.status_code == 200
    assert len(recorder.requests) == 1


async def test_multipart_transcription_model_is_checked(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings, models=["whisper-1"])
    client, _ = build_proxy()
    allowed_body = (
        b"--B\r\n"
        b'Content-Disposition: form-data; name="model"\r\n\r\n'
        b"whisper-1\r\n"
        b"--B--\r\n"
    )
    forbidden_body = allowed_body.replace(b"whisper-1", b"whisper-9")
    headers = {**auth(secret), "content-type": "multipart/form-data; boundary=B"}
    async with client:
        ok = await client.post("/v1/audio/transcriptions", headers=headers, content=allowed_body)
        ng = await client.post("/v1/audio/transcriptions", headers=headers, content=forbidden_body)

    assert ok.status_code == 200
    assert ng.status_code == 403
    assert len(recorder.requests) == 1
