import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { getAuthUser } from "../auth/index.js";
import type { AppDatabase } from "../db/database.js";
import { createMessageRepository, createSessionRepository } from "./repository.js";

const createSessionBody = z.object({
  title: z.string().min(1).max(120).optional(),
});

const updateSessionBody = z.object({
  title: z.string().trim().min(1).max(120),
});

const updateSystemPromptBody = z.object({
  // Empty / whitespace-only strings clear the override; null is a no-op.
  system_prompt: z.string().max(8_000).nullable().optional(),
});

export async function registerSessionRoutes(app: FastifyInstance, db: AppDatabase) {
  const sessions = createSessionRepository(db);
  const messages = createMessageRepository(db);

  app.get("/api/sessions", async (request) => {
    const user = await getAuthUser(request);
    return { items: sessions.list(user.id) };
  });

  app.post("/api/sessions", async (request, reply) => {
    const user = await getAuthUser(request);
    const body = createSessionBody.parse(request.body ?? {});
    const session = sessions.create(user.id, body.title ?? null);
    return reply.code(201).send(session);
  });

  app.get<{ Params: { id: string } }>("/api/sessions/:id", async (request, reply) => {
    const user = await getAuthUser(request);
    const session = sessions.get(user.id, request.params.id);

    if (!session) {
      return reply.code(404).send({ error: "session_not_found" });
    }

    return {
      ...session,
      messages: messages.listForSession(user.id, session.id),
    };
  });

  app.patch<{ Params: { id: string } }>("/api/sessions/:id", async (request, reply) => {
    const user = await getAuthUser(request);
    const body = updateSessionBody.parse(request.body);
    const session = sessions.get(user.id, request.params.id);

    if (!session) {
      return reply.code(404).send({ error: "session_not_found" });
    }

    sessions.updateTitle(user.id, session.id, body.title);
    return sessions.get(user.id, session.id);
  });

  // Per-session system prompt override. Inserted into the chat stream
  // immediately after the base identity prompt, before the docs/actions
  // context. Use to attach incident-specific context (runbook link, on-call
  // names, severity matrix) without polluting the global prompt.
  app.patch<{ Params: { id: string } }>(
    "/api/sessions/:id/system-prompt",
    async (request, reply) => {
      const user = await getAuthUser(request);
      const body = updateSystemPromptBody.parse(request.body ?? {});
      const session = sessions.get(user.id, request.params.id);

      if (!session) {
        return reply.code(404).send({ error: "session_not_found" });
      }

      sessions.updateSystemPrompt(user.id, session.id, body.system_prompt ?? null);
      return sessions.get(user.id, session.id);
    },
  );

  app.delete<{ Params: { id: string } }>("/api/sessions/:id", async (request, reply) => {
    const user = await getAuthUser(request);
    const deleted = sessions.delete(user.id, request.params.id);

    if (!deleted) {
      return reply.code(404).send({ error: "session_not_found" });
    }

    return reply.code(204).send();
  });

  // Bulk delete: wipes every session owned by the current user. Cascade
  // removes the messages and webhook_runs that point at them. The frontend
  // requires the user to type the literal word "delete" before this fires.
  app.delete("/api/sessions", async (request, reply) => {
    const user = await getAuthUser(request);
    const removed = sessions.deleteAllForUser(user.id);
    return reply.code(200).send({ deleted: removed });
  });

  // Export: returns a JSON document with the session metadata and all its
  // messages. The shape is a stable contract so a `POST /api/sessions/import`
  // can read it back. webhook_runs are intentionally excluded from the
  // export — those are operational audit data, not conversation content.
  app.get<{ Params: { id: string } }>("/api/sessions/:id/export", async (request, reply) => {
    const user = await getAuthUser(request);
    const session = sessions.get(user.id, request.params.id);

    if (!session) {
      return reply.code(404).send({ error: "session_not_found" });
    }

    return {
      version: 1,
      exported_at: new Date().toISOString(),
      session: {
        id: session.id,
        title: session.title,
        created_at: session.created_at,
        updated_at: session.updated_at,
      },
      messages: messages.listForSession(user.id, session.id),
    };
  });

  // Import: accepts the export document above, creates a new session owned
  // by the caller, and writes the messages with fresh ids. Returns the new
  // session id and a count of imported messages.
  const importSessionBody = z.object({
    session: z.object({
      title: z.string().max(120).nullable().optional(),
      created_at: z.string().optional(),
      updated_at: z.string().optional(),
    }),
    messages: z.array(
      z.object({
        role: z.enum(["user", "assistant", "system"]),
        content: z.string().min(1).max(50_000),
        metadata: z.record(z.unknown()).optional(),
        // Original created_at is preserved if present; otherwise "now" is
        // used. Used only to restore the timeline.
        created_at: z.string().optional(),
      }),
    ),
  });

  app.post("/api/sessions/import", async (request, reply) => {
    const user = await getAuthUser(request);
    const body = importSessionBody.parse(request.body);

    const newSession = sessions.create(user.id, body.session.title ?? null);
    let imported = 0;
    for (const message of body.messages) {
      messages.create({
        sessionId: newSession.id,
        userId: user.id,
        role: message.role,
        content: message.content,
        metadata: message.metadata,
      });
      imported += 1;
    }
    sessions.touch(user.id, newSession.id);

    return reply.code(201).send({
      session: newSession,
      imported_messages: imported,
    });
  });
}
