"""
BIBIVIDI — Mock AI API
Simulates Anthropic and OpenAI APIs locally on port 9090.
Zero API credits consumed. Used for development and CI.

Usage:
  uvicorn mock_api:app --port 9090

Then point BIBIVIDI at it:
  BIBIVIDI_TARGET_APIS='["localhost:9090"]' bibividi start
"""
from __future__ import annotations

import asyncio
import json
import time
import uuid

from fastapi import FastAPI, Request, Response
from fastapi.responses import StreamingResponse

app = FastAPI(title="BIBIVIDI Mock API", docs_url=None, redoc_url=None)

_MOCK_TEXT = "This is a mock response from BIBIVIDI test API. The proxy is working correctly."


# ── Anthropic ─────────────────────────────────────────────────────────────────

@app.post("/v1/messages")
async def anthropic_messages(request: Request) -> Response:
    body = await request.json()
    streaming = body.get("stream", False)
    model = body.get("model", "claude-haiku-4-5-20251001")
    max_tokens = body.get("max_tokens", 100)

    # Reflect the prompt back so tests can verify injection
    messages = body.get("messages", [])
    prompt_echo = ""
    for msg in reversed(messages):
        if msg.get("role") == "user":
            content = msg.get("content", "")
            prompt_echo = content[:60] if isinstance(content, str) else str(content)[:60]
            break

    text = f"{_MOCK_TEXT} [echo: {prompt_echo}]"

    if streaming:
        return StreamingResponse(
            _anthropic_sse(text, model),
            media_type="text/event-stream",
        )

    return Response(
        content=json.dumps(_anthropic_response(text, model, max_tokens)).encode(),
        media_type="application/json",
    )


def _anthropic_response(text: str, model: str, max_tokens: int) -> dict:
    return {
        "id": f"msg_{uuid.uuid4().hex[:24]}",
        "type": "message",
        "role": "assistant",
        "model": model,
        "content": [{"type": "text", "text": text}],
        "stop_reason": "end_turn",
        "stop_sequence": None,
        "usage": {
            "input_tokens": 42,
            "output_tokens": min(max_tokens, len(text.split())),
        },
    }


async def _anthropic_sse(text: str, model: str):
    msg_id = f"msg_{uuid.uuid4().hex[:24]}"

    events = [
        {"type": "message_start", "message": {
            "id": msg_id, "type": "message", "role": "assistant",
            "model": model, "content": [], "stop_reason": None,
            "usage": {"input_tokens": 42, "output_tokens": 0},
        }},
        {"type": "content_block_start", "index": 0,
         "content_block": {"type": "text", "text": ""}},
        {"type": "ping"},
    ]

    for event in events:
        yield f"event: {event['type']}\ndata: {json.dumps(event)}\n\n"
        await asyncio.sleep(0)

    # Stream text word by word
    words = text.split()
    for i, word in enumerate(words):
        chunk = word + (" " if i < len(words) - 1 else "")
        delta = {"type": "content_block_delta", "index": 0,
                 "delta": {"type": "text_delta", "text": chunk}}
        yield f"event: content_block_delta\ndata: {json.dumps(delta)}\n\n"
        await asyncio.sleep(0.01)

    stop_events = [
        {"type": "content_block_stop", "index": 0},
        {"type": "message_delta",
         "delta": {"stop_reason": "end_turn", "stop_sequence": None},
         "usage": {"output_tokens": len(words)}},
        {"type": "message_stop"},
    ]
    for event in stop_events:
        yield f"event: {event['type']}\ndata: {json.dumps(event)}\n\n"
        await asyncio.sleep(0)

    yield "data: [DONE]\n\n"


# ── OpenAI ────────────────────────────────────────────────────────────────────

@app.post("/v1/chat/completions")
async def openai_chat(request: Request) -> Response:
    body = await request.json()
    streaming = body.get("stream", False)
    model = body.get("model", "gpt-4o")

    messages = body.get("messages", [])
    prompt_echo = ""
    for msg in reversed(messages):
        if msg.get("role") == "user":
            content = msg.get("content", "")
            prompt_echo = content[:60] if isinstance(content, str) else str(content)[:60]
            break

    text = f"{_MOCK_TEXT} [echo: {prompt_echo}]"

    if streaming:
        return StreamingResponse(
            _openai_sse(text, model),
            media_type="text/event-stream",
        )

    return Response(
        content=json.dumps(_openai_response(text, model)).encode(),
        media_type="application/json",
    )


def _openai_response(text: str, model: str) -> dict:
    return {
        "id": f"chatcmpl-{uuid.uuid4().hex[:24]}",
        "object": "chat.completion",
        "created": int(time.time()),
        "model": model,
        "choices": [{
            "index": 0,
            "message": {"role": "assistant", "content": text},
            "finish_reason": "stop",
        }],
        "usage": {"prompt_tokens": 42, "completion_tokens": len(text.split()), "total_tokens": 0},
    }


async def _openai_sse(text: str, model: str):
    completion_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
    created = int(time.time())

    # Role delta
    role_chunk = {
        "id": completion_id, "object": "chat.completion.chunk",
        "created": created, "model": model,
        "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": None}],
    }
    yield f"data: {json.dumps(role_chunk)}\n\n"
    await asyncio.sleep(0)

    # Content chunks
    words = text.split()
    for i, word in enumerate(words):
        chunk_text = word + (" " if i < len(words) - 1 else "")
        chunk = {
            "id": completion_id, "object": "chat.completion.chunk",
            "created": created, "model": model,
            "choices": [{"index": 0, "delta": {"content": chunk_text}, "finish_reason": None}],
        }
        yield f"data: {json.dumps(chunk)}\n\n"
        await asyncio.sleep(0.01)

    # Stop chunk
    stop_chunk = {
        "id": completion_id, "object": "chat.completion.chunk",
        "created": created, "model": model,
        "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
    }
    yield f"data: {json.dumps(stop_chunk)}\n\n"
    yield "data: [DONE]\n\n"


# ── Health ────────────────────────────────────────────────────────────────────

@app.get("/health")
async def health():
    return {"status": "ok", "service": "bibividi-mock-api"}
