from __future__ import annotations

import datetime
import json
from typing import Any, Dict

import httpx
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse, StreamingResponse

from gptmock.core.dependencies import get_settings
from gptmock.core.logging import log_json
from gptmock.core.settings import Settings
from gptmock.schemas.transform import convert_ollama_messages, normalize_ollama_tools
from gptmock.services.chat import ChatCompletionError, process_chat_completion
from gptmock.services.model_registry import OLLAMA_FAKE_EVAL, get_ollama_models

router = APIRouter()


@router.get("/api/version")
async def ollama_version(
    settings: Settings = Depends(get_settings),
):
    """Return Ollama version."""
    if settings.verbose:
        print("IN GET /api/version")
    
    version = settings.ollama_version
    payload = {"version": version}
    
    if settings.verbose:
        log_json("OUT GET /api/version", payload, logger=print)
    
    return JSONResponse(payload)


@router.get("/api/tags")
async def ollama_tags(
    settings: Settings = Depends(get_settings),
):
    """List available models in Ollama format."""
    if settings.verbose:
        print("IN GET /api/tags")
    
    models = get_ollama_models(expose_reasoning=settings.expose_reasoning_models)
    
    payload = {"models": models}
    
    if settings.verbose:
        log_json("OUT GET /api/tags", payload, logger=print)
    
    return JSONResponse(payload)


@router.post("/api/show")
async def ollama_show(
    request: Request,
    settings: Settings = Depends(get_settings),
):
    """Show model details."""
    # Parse request
    try:
        raw_body = await request.body()
        raw_text = raw_body.decode("utf-8")
        
        if settings.verbose:
            print(f"IN POST /api/show\n{raw_text}")
        
        payload = json.loads(raw_text) if raw_text else {}
    except json.JSONDecodeError:
        return JSONResponse({"error": "Invalid JSON"}, status_code=400)
    
    model = payload.get("model")
    if not isinstance(model, str) or not model.strip():
        err = {"error": "Model not found"}
        if settings.verbose:
            log_json("OUT POST /api/show", err, logger=print)
        return JSONResponse(err, status_code=400)
    
    # Return hardcoded model info (from routes_ollama.py:184-202)
    response = {
        "modelfile": "# Modelfile generated by \"ollama show\"\n# To build a new Modelfile based on this one, replace the FROM line with:\n# FROM llava:latest\n\nFROM /models/blobs/sha256:placeholder\nTEMPLATE \"\"\"{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: \"\"\"\nPARAMETER num_ctx 100000\nPARAMETER stop \"</s>\"\nPARAMETER stop \"USER:\"\nPARAMETER stop \"ASSISTANT:\"",
        "parameters": "num_keep 24\nstop \"<|start_header_id|>\"\nstop \"<|end_header_id|>\"\nstop \"<|eot_id|>\"",
        "template": "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>",
        "details": {
            "parent_model": "",
            "format": "gguf",
            "family": "llama",
            "families": ["llama"],
            "parameter_size": "8.0B",
            "quantization_level": "Q4_0",
        },
        "model_info": {
            "general.architecture": "llama",
            "general.file_type": 2,
            "llama.context_length": 2000000,
        },
        "capabilities": ["completion", "vision", "tools", "thinking"],
    }
    
    if settings.verbose:
        log_json("OUT POST /api/show", response, logger=print)
    
    return JSONResponse(response)


@router.post("/api/chat")
async def ollama_chat(
    request: Request,
    settings: Settings = Depends(get_settings),
):
    """
    Ollama-compatible chat endpoint.
    
    Converts Ollama format → OpenAI format → calls service → converts back to Ollama ndjson.
    """
    # 1. Parse Ollama request
    try:
        raw_body = await request.body()
        raw_text = raw_body.decode("utf-8")
        
        if settings.verbose:
            print(f"IN POST /api/chat\n{raw_text}")
        
        ollama_payload = json.loads(raw_text) if raw_text else {}
    except json.JSONDecodeError:
        return JSONResponse({"error": "Invalid JSON body"}, status_code=400)
    
    model = ollama_payload.get("model")
    raw_messages = ollama_payload.get("messages")
    
    if not isinstance(model, str) or not isinstance(raw_messages, list) or not raw_messages:
        err = {"error": "Invalid request format"}
        if settings.verbose:
            log_json("OUT POST /api/chat", err, logger=print)
        return JSONResponse(err, status_code=400)
    
    # 2. Convert Ollama → OpenAI format
    messages = convert_ollama_messages(
        raw_messages,
        ollama_payload.get("images") if isinstance(ollama_payload.get("images"), list) else None
    )
    
    # Move system messages to user role (ChatGPT doesn't support system role)
    if isinstance(messages, list):
        sys_idx = next((i for i, m in enumerate(messages) if isinstance(m, dict) and m.get("role") == "system"), None)
        if isinstance(sys_idx, int):
            sys_msg = messages.pop(sys_idx)
            content = sys_msg.get("content") if isinstance(sys_msg, dict) else ""
            messages.insert(0, {"role": "user", "content": content})
    
    stream_req = ollama_payload.get("stream")
    if stream_req is None:
        stream_req = True
    stream_req = bool(stream_req)
    
    # Build OpenAI payload
    openai_payload = {
        "model": model,
        "messages": messages,
        "stream": stream_req,
    }
    
    # Handle tools
    tools_req = ollama_payload.get("tools") if isinstance(ollama_payload.get("tools"), list) else []
    if tools_req:
        openai_tools = normalize_ollama_tools(tools_req)
        if openai_tools:
            openai_payload["tools"] = openai_tools
    
    tool_choice = ollama_payload.get("tool_choice", "auto")
    if tool_choice in ("auto", "none"):
        openai_payload["tool_choice"] = tool_choice
    
    parallel_tool_calls = bool(ollama_payload.get("parallel_tool_calls", False))
    if parallel_tool_calls:
        openai_payload["parallel_tool_calls"] = parallel_tool_calls
    
    # Handle responses_tools (web_search) via gptmock extension fields
    responses_tools_payload = ollama_payload.get("responses_tools") if isinstance(ollama_payload.get("responses_tools"), list) else []
    if responses_tools_payload:
        openai_payload["responses_tools"] = responses_tools_payload
    
    responses_tool_choice = ollama_payload.get("responses_tool_choice")
    if isinstance(responses_tool_choice, str) and responses_tool_choice in ("auto", "none"):
        openai_payload["responses_tool_choice"] = responses_tool_choice
    
    # 3. Create HTTP client and call service
    http_client = httpx.AsyncClient()
    
    try:
        response, is_streaming = await process_chat_completion(
            payload=openai_payload,
            settings=settings,
            http_client=http_client,
        )
        
        # 4. Convert response to Ollama format
        if is_streaming:
            # Create async generator that converts OpenAI SSE → Ollama ndjson
            async def ollama_stream_generator():
                try:
                    async for sse_chunk in response:
                        # sse_chunk is bytes like: b'data: {...}\n\n'
                        if not sse_chunk.startswith(b"data: "):
                            continue
                        
                        json_bytes = sse_chunk[6:].strip()  # Remove "data: " prefix
                        
                        if json_bytes == b"[DONE]":
                            # Send final done message
                            done_chunk = {
                                "model": model,
                                "created_at": datetime.datetime.utcnow().isoformat() + "Z",
                                "message": {"role": "assistant", "content": ""},
                                "done": True,
                                **OLLAMA_FAKE_EVAL,
                            }
                            yield (json.dumps(done_chunk) + "\n").encode("utf-8")
                            break
                        
                        try:
                            openai_chunk = json.loads(json_bytes)
                            choices = openai_chunk.get("choices", [])
                            
                            if choices:
                                delta = choices[0].get("delta", {})
                                content = delta.get("content", "")
                                
                                # Only send chunks with content
                                if content:
                                    ollama_chunk = {
                                        "model": model,
                                        "created_at": datetime.datetime.utcnow().isoformat() + "Z",
                                        "message": {"role": "assistant", "content": content},
                                        "done": False,
                                    }
                                    yield (json.dumps(ollama_chunk) + "\n").encode("utf-8")
                        except Exception:
                            # Skip malformed chunks
                            continue
                finally:
                    await http_client.aclose()
            
            if settings.verbose:
                print("OUT POST /api/chat (streaming response)")
            
            return StreamingResponse(
                ollama_stream_generator(),
                media_type="application/x-ndjson",
            )
        else:
            # Convert OpenAI response dict → Ollama response dict
            await http_client.aclose()
            
            choice = response.get("choices", [{}])[0]
            message = choice.get("message", {})
            
            ollama_response = {
                "model": model,
                "created_at": datetime.datetime.utcnow().isoformat() + "Z",
                "message": message,
                "done": True,
                "done_reason": choice.get("finish_reason", "stop"),
                **OLLAMA_FAKE_EVAL,
            }
            
            if settings.verbose:
                log_json("OUT POST /api/chat", ollama_response, logger=print)
            
            return JSONResponse(ollama_response)
            
    except ChatCompletionError as e:
        await http_client.aclose()
        error_response = {"error": e.message}
        if settings.verbose:
            log_json("OUT POST /api/chat ERROR", error_response, logger=print)
        return JSONResponse(error_response, status_code=e.status_code)
    except Exception as e:
        await http_client.aclose()
        raise
