import { type Router } from "express";
import { z } from "zod";

import { getAgentAuthContext } from "../auth/agentAuth.js";
import { logger } from "../logger.js";
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
import { type SessionMetadataStore } from "../sessions/metadataStore.js";
import { type SessionUiStateStore } from "../sessions/uiStateStore.js";
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
import {
  type ActiveRun,
  toPermissionStatus,
  updateLastAssistantPermission,
  updateLastAssistantQuestion,
} from "./chatUiState.js";

const permissionReplyPayloadSchema = z.object({
  request_id: z.string().min(1),
  reply: z.enum(["once", "always", "reject"]),
  message: z.string().max(1000).optional(),
});

const questionReplyPayloadSchema = z.object({
  request_id: z.string().min(1),
  action: z.enum(["reply", "reject"]).default("reply"),
  answers: z.array(z.array(z.string().max(2000))).default([]),
});

type RegisterInteractionRoutesOptions = {
  activeRuns: Map<string, ActiveRun>;
  runtime: OpencodeRuntimeAdapter;
  sessionMetadataStore: SessionMetadataStore;
  sessionUiStateStore: SessionUiStateStore;
};

const toSessionUiStateContext = (sessionId: string) => ({
  sessionId,
});

export const registerChatInteractionRoutes = (
  chatRouter: Router,
  {
    activeRuns,
    runtime,
    sessionMetadataStore,
    sessionUiStateStore,
  }: RegisterInteractionRoutesOptions,
) => {
  chatRouter.post("/sessions/:session_id/permission-responses", async (req, res) => {
    const parsed = permissionReplyPayloadSchema.safeParse(req.body);
    if (!parsed.success) {
      res.status(400).json({
        message: "invalid request payload",
        detail: parsed.error.flatten(),
      });
      return;
    }

    try {
      const authContext = getAgentAuthContext(req);
      const projectId = authContext.projectId;
      const userId = authContext.userId;
      const actorKey = toActorKey(userId);
      const projectKey = toProjectKey(projectId);
      const requestId = parsed.data.request_id;
      const sessionRecord = await sessionMetadataStore.get(
        { actorKey, projectId, projectKey, userId },
        req.params.session_id,
      );
      if (!sessionRecord) {
        res.status(404).json({ message: "session not found" });
        return;
      }

      const run = activeRuns.get(sessionRecord.sessionId);
      if (!run || run.status !== "running") {
        res.status(409).json({ message: "session is not waiting for permissions" });
        return;
      }

      const pendingPermission = run.pendingPermissions.get(requestId);
      if (!pendingPermission) {
        res.status(404).json({ message: "permission request not found" });
        return;
      }
      const persistPermissionState = async () => {
        const currentState = await sessionUiStateStore.read(
          toSessionUiStateContext(sessionRecord.sessionId),
        );
        await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
          sessionId: sessionRecord.sessionId,
          isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
          messages: run.messages,
        });
      };

      try {
        await runtime.replyPermission({
          requestId,
          sessionId: sessionRecord.sessionId,
          reply: parsed.data.reply,
          message: parsed.data.message,
        });
      } catch (error) {
        run.messages = updateLastAssistantPermission(
          run.messages,
          requestId,
          (permission) => ({
            ...permission,
            status: "error",
            error:
              error instanceof Error
                ? error.message
                : "failed to reply permission",
          }),
        );
        await persistPermissionState().catch((persistError) => {
          logger.warn(
            { err: persistError, sessionId: sessionRecord.sessionId },
            "failed to persist permission error state",
          );
        });
        res.status(502).json({
          message: "permission reply failed",
          detail: error instanceof Error ? error.message : String(error),
        });
        return;
      }

      run.pendingPermissions.delete(requestId);
      const status = toPermissionStatus(parsed.data.reply);
      run.messages = updateLastAssistantPermission(
        run.messages,
        requestId,
        (permission) => ({
          ...permission,
          status,
          repliedAt: Date.now(),
        }),
      );
      await persistPermissionState().catch((persistError) => {
        logger.warn(
          { err: persistError, sessionId: sessionRecord.sessionId },
          "failed to persist permission reply state",
        );
      });
      for (const subscriber of run.subscribers) {
        subscriber.write("permission_response", {
          session_id: sessionRecord.sessionId,
          request_id: requestId,
          reply: parsed.data.reply,
        });
      }

      res.status(202).json({
        session_id: sessionRecord.sessionId,
        request_id: requestId,
        reply: parsed.data.reply,
      });
    } catch (error) {
      const detail = error instanceof Error ? error.message : String(error);
      logger.error({ err: error }, "permission reply route failed");
      res.status(500).json({
        message: "permission reply route failed",
        detail,
      });
    }
  });

  chatRouter.post("/sessions/:session_id/question-responses", async (req, res) => {
    const parsed = questionReplyPayloadSchema.safeParse(req.body);
    if (!parsed.success) {
      res.status(400).json({
        message: "invalid request payload",
        detail: parsed.error.flatten(),
      });
      return;
    }

    try {
      const authContext = getAgentAuthContext(req);
      const projectId = authContext.projectId;
      const userId = authContext.userId;
      const actorKey = toActorKey(userId);
      const projectKey = toProjectKey(projectId);
      const requestId = parsed.data.request_id;
      const sessionRecord = await sessionMetadataStore.get(
        { actorKey, projectId, projectKey, userId },
        req.params.session_id,
      );
      if (!sessionRecord) {
        res.status(404).json({ message: "session not found" });
        return;
      }

      const run = activeRuns.get(sessionRecord.sessionId);
      if (!run) {
        res.status(409).json({ message: "session is not waiting for questions" });
        return;
      }

      const pendingQuestion = run.pendingQuestions.get(requestId);
      if (!pendingQuestion) {
        res.status(404).json({ message: "question request not found" });
        return;
      }
      const persistQuestionState = async () => {
        const currentState = await sessionUiStateStore.read(
          toSessionUiStateContext(sessionRecord.sessionId),
        );
        await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
          sessionId: sessionRecord.sessionId,
          isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
          messages: run.messages,
        });
      };

      try {
        if (parsed.data.action === "reject") {
          await runtime.rejectQuestion({
            requestId,
            sessionId: sessionRecord.sessionId,
          });
        } else {
          await runtime.replyQuestion({
            requestId,
            sessionId: sessionRecord.sessionId,
            answers: parsed.data.answers,
          });
        }
      } catch (error) {
        run.messages = updateLastAssistantQuestion(
          run.messages,
          requestId,
          (question) => ({
            ...question,
            status: "error",
            error:
              error instanceof Error
                ? error.message
                : `failed to ${parsed.data.action} question`,
          }),
        );
        await persistQuestionState().catch((persistError) => {
          logger.warn(
            { err: persistError, sessionId: sessionRecord.sessionId },
            "failed to persist question error state",
          );
        });
        res.status(502).json({
          message: `question ${parsed.data.action} failed`,
          detail: error instanceof Error ? error.message : String(error),
        });
        return;
      }

      run.pendingQuestions.delete(requestId);
      run.messages = updateLastAssistantQuestion(
        run.messages,
        requestId,
        (question) => ({
          ...question,
          status: parsed.data.action === "reject" ? "rejected" : "answered",
          answers:
            parsed.data.action === "reject" ? question.answers : parsed.data.answers,
          repliedAt: Date.now(),
          error: undefined,
        }),
      );
      await persistQuestionState().catch((persistError) => {
        logger.warn(
          { err: persistError, sessionId: sessionRecord.sessionId },
          "failed to persist question reply state",
        );
      });
      for (const subscriber of run.subscribers) {
        subscriber.write("question_response", {
          session_id: pendingQuestion.session_id,
          request_id: requestId,
          ...(parsed.data.action === "reject"
            ? { rejected: true }
            : { answers: parsed.data.answers }),
        });
      }
      if (
        run.status !== "running" &&
        run.pendingPermissions.size === 0 &&
        run.pendingQuestions.size === 0
      ) {
        activeRuns.delete(sessionRecord.sessionId);
      }

      res.status(202).json({
        session_id: pendingQuestion.session_id,
        request_id: requestId,
        ...(parsed.data.action === "reject"
          ? { rejected: true }
          : { answers: parsed.data.answers }),
      });
    } catch (error) {
      const detail = error instanceof Error ? error.message : String(error);
      logger.error({ err: error }, "question response route failed");
      res.status(500).json({
        message: "question response route failed",
        detail,
      });
    }
  });
};
