import { NextResponse } from "next/server";
import {
  buildUpstreamMessages,
  extractAssistantTextContent,
  extractObjective5Metrics,
  extractSvgMarkup,
  getOllamaChatEndpointCandidates,
  isLocalEndpoint,
  looksLikeOllamaModel,
  normalizeUpstreamChatEndpoint,
  sanitizeSvgDocument,
  type Objective5Message,
} from "~/lib/lab2-chat";

type ChatRouteRequestBody = {
  apiKey?: string;
  endpoint?: string;
  messages?: Objective5Message[];
  model?: string;
};

const OPENAI_UPSTREAM_TIMEOUT_MS = 30000;
const OLLAMA_UPSTREAM_TIMEOUT_MS = 90000;
const LOCAL_OPENAI_UPSTREAM_TIMEOUT_MS = 90000;

export async function POST(request: Request) {
  let body: ChatRouteRequestBody;

  try {
    body = (await request.json()) as ChatRouteRequestBody;
  } catch {
    return NextResponse.json(
      {
        error: "The request body must be valid JSON.",
      },
      { status: 400 },
    );
  }

  const endpoint = body.endpoint?.trim();
  const apiKey = body.apiKey?.trim();
  const model = body.model?.trim();

  if (!endpoint) {
    return NextResponse.json(
      {
        error: "An endpoint is required.",
      },
      { status: 400 },
    );
  }

  if (!model) {
    return NextResponse.json(
      {
        error: "A model is required.",
      },
      { status: 400 },
    );
  }

  if (!Array.isArray(body.messages) || body.messages.length === 0) {
    return NextResponse.json(
      {
        error: "At least one chat message is required.",
      },
      { status: 400 },
    );
  }

  const useOllamaChat = looksLikeOllamaModel(model);
  const useLocalOpenAI = !useOllamaChat && isLocalEndpoint(endpoint);
  let upstreamCandidates: string[];
  try {
    upstreamCandidates = useOllamaChat
      ? getOllamaChatEndpointCandidates(endpoint)
      : [normalizeUpstreamChatEndpoint(endpoint)];
  } catch {
    return NextResponse.json(
      {
        error: "The endpoint must be a valid URL.",
      },
      { status: 400 },
    );
  }

  const upstreamTimeoutMs = useOllamaChat
    ? OLLAMA_UPSTREAM_TIMEOUT_MS
    : useLocalOpenAI
      ? LOCAL_OPENAI_UPSTREAM_TIMEOUT_MS
      : OPENAI_UPSTREAM_TIMEOUT_MS;

  try {
    let lastStatus = 502;
    let lastMessage = "The chat request could not reach the upstream endpoint.";

    for (const upstreamUrl of upstreamCandidates) {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), upstreamTimeoutMs);

      try {
        const upstreamResponse = await fetch(upstreamUrl, {
          body: JSON.stringify(
            useOllamaChat
              ? {
                  messages: buildUpstreamMessages(body.messages),
                  model,
                  stream: false,
                }
              : {
                  messages: buildUpstreamMessages(body.messages),
                  model,
                  stream: false,
                  temperature: 0.8,
                },
          ),
          headers: {
            ...(apiKey
              ? {
                  Authorization: `Bearer ${apiKey}`,
                }
              : {}),
            "Content-Type": "application/json",
          },
          method: "POST",
          signal: controller.signal,
        });

        const responseText = await upstreamResponse.text();
        let parsedBody: unknown = null;

        try {
          parsedBody = JSON.parse(responseText);
        } catch {
          parsedBody = null;
        }

        if (!upstreamResponse.ok) {
          lastStatus = upstreamResponse.status;
          lastMessage =
            typeof parsedBody === "object" &&
            parsedBody !== null &&
            "error" in parsedBody &&
            typeof parsedBody.error === "object" &&
            parsedBody.error !== null &&
            "message" in parsedBody.error &&
            typeof parsedBody.error.message === "string"
              ? parsedBody.error.message
              : `The upstream endpoint returned ${upstreamResponse.status}.`;
          continue;
        }

        if (!parsedBody || typeof parsedBody !== "object") {
          lastStatus = 502;
          lastMessage = "The upstream endpoint returned an unreadable response.";
          continue;
        }

        const content = extractAssistantTextContent(parsedBody);
        const metrics = extractObjective5Metrics(parsedBody);
        if (!content) {
          lastStatus = 502;
          lastMessage = "The upstream endpoint returned no assistant content.";
          continue;
        }

        const svgMarkup = extractSvgMarkup(content);
        if (!svgMarkup) {
          return NextResponse.json({
            content,
            metrics,
            renderMode: "text",
            role: "assistant",
          });
        }

        const sanitizedSvg = sanitizeSvgDocument(svgMarkup);
        if (!sanitizedSvg.ok) {
          return NextResponse.json({
            content,
            error: `${sanitizedSvg.error} Showing the raw response instead.`,
            metrics,
            renderMode: "text",
            role: "assistant",
          });
        }

        return NextResponse.json({
          content,
          metrics,
          renderMode: "svg",
          role: "assistant",
          svg: sanitizedSvg.svg,
        });
      } catch (caughtError) {
        if (caughtError instanceof Error && caughtError.name === "AbortError") {
          lastStatus = 504;
          lastMessage = `The upstream endpoint timed out after ${Math.floor(upstreamTimeoutMs / 1000)} seconds.`;
          continue;
        }

        lastStatus = 502;
        lastMessage = "The chat request could not reach the upstream endpoint.";
      } finally {
        clearTimeout(timeoutId);
      }
    }

    return NextResponse.json(
      {
        error: lastMessage,
      },
      { status: lastStatus },
    );
  } catch (caughtError) {
    if (caughtError instanceof Error && caughtError.name === "AbortError") {
      return NextResponse.json(
        {
          error: `The upstream endpoint timed out after ${Math.floor(upstreamTimeoutMs / 1000)} seconds.`,
        },
        { status: 504 },
      );
    }

    return NextResponse.json(
      {
        error: "The chat request could not reach the upstream endpoint.",
      },
      { status: 502 },
    );
  }
}
