import { prisma } from "../../lib/db";
import { makeResponse } from "../../lib/response";
import { ERROR_MESSAGES } from "../../lib/errors";
import { subscribeToLogs } from "../../services/deploy";
import { streamAppLogs } from "../../services/appLogs";
import { resolveWsAuth } from "../../lib/middlewares/auth";
import { computePreviewCostUsd, hourlyRateUsd } from "../../lib/cost";
import { fetchRepo } from "../../services/gitea";

function requireAuth(ctr: any) {
  const auth = ctr.getAuth?.();
  if (!auth?.success) return null;
  return auth.user;
}

export async function listPreviews(ctr: any) {
  const user = requireAuth(ctr);
  if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });

  const previews = await prisma.preview.findMany({
    where: { repoConfig: { userId: user.id } },
    include: { repoConfig: { select: { repoOwner: true, repoName: true } } },
    orderBy: { updatedAt: "desc" },
  });

  return makeResponse({
    ctr, content: {
      code: 200, data: previews.map(p => ({
        id: p.id,
        prNumber: p.prNumber,
        prTitle: p.prTitle,
        commitSha: p.commitSha,
        status: p.status,
        stopReason: p.stopReason,
        instanceIp: p.instanceIp,
        port: p.port,
        createdAt: p.createdAt,
        updatedAt: p.updatedAt,
        lastActivityAt: p.lastActivityAt,
        repoOwner: p.repoConfig.repoOwner,
        repoName: p.repoConfig.repoName,
        instanceType: p.instanceType,
        costUsd: computePreviewCostUsd(p),
      }))
    }
  });
}

export async function getPreview(ctr: any) {
  const user = requireAuth(ctr);
  if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });

  const id = parseInt(ctr.params.get("id") || "0", 10);
  const preview = await prisma.preview.findFirst({
    where: { id, repoConfig: { userId: user.id } },
    include: {
      repoConfig: { select: { repoOwner: true, repoName: true } },
      jobs: { orderBy: { createdAt: "desc" }, take: 10 },
    },
  });

  if (!preview) return makeResponse({ ctr, content: { code: 404, message: ERROR_MESSAGES.NOT_FOUND.message } });

  return makeResponse({
    ctr, content: {
      code: 200, data: {
        id: preview.id,
        prNumber: preview.prNumber,
        prTitle: preview.prTitle,
        commitSha: preview.commitSha,
        status: preview.status,
        stopReason: preview.stopReason,
        instanceIp: preview.instanceIp,
        port: preview.port,
        logs: preview.logs,
        createdAt: preview.createdAt,
        updatedAt: preview.updatedAt,
        stoppedAt: preview.stoppedAt,
        lastActivityAt: preview.lastActivityAt,
        repoOwner: preview.repoConfig.repoOwner,
        repoName: preview.repoConfig.repoName,
        instanceType: preview.instanceType,
        costUsd: computePreviewCostUsd(preview),
        costRateUsd: preview.instanceLaunchedAt ? hourlyRateUsd(preview.instanceType) : 0,
        jobs: preview.jobs.map(j => ({
          id: j.id,
          type: j.type,
          status: j.status,
          createdAt: j.createdAt,
          startedAt: j.startedAt,
          finishedAt: j.finishedAt,
          error: j.error,
        })),
      }
    }
  });
}

export async function stopPreviewRoute(ctr: any) {
  const user = requireAuth(ctr);
  if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });

  const id = parseInt(ctr.params.get("id") || "0", 10);
  const preview = await prisma.preview.findFirst({
    where: { id, repoConfig: { userId: user.id } },
  });

  if (!preview) return makeResponse({ ctr, content: { code: 404, message: ERROR_MESSAGES.NOT_FOUND.message } });
  if (preview.status === "STOPPED") return makeResponse({ ctr, content: { code: 400, message: "Already stopped" } });

  await prisma.job.create({
    data: { previewId: id, type: "STOP", status: "PENDING", payload: { reason: "Manual stop via UI" } },
  });

  return makeResponse({ ctr, content: { code: 200, message: "Stop job enqueued" } });
}

export async function rebuildPreviewRoute(ctr: any) {
  const user = requireAuth(ctr);
  if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } });

  const id = parseInt(ctr.params.get("id") || "0", 10);
  const preview = await prisma.preview.findFirst({
    where: { id, repoConfig: { userId: user.id } },
    include: { repoConfig: { include: { user: true } } },
  });

  if (!preview) return makeResponse({ ctr, content: { code: 404, message: ERROR_MESSAGES.NOT_FOUND.message } });
  if (preview.status === "IGNORED") {
    return makeResponse({ ctr, content: { code: 400, message: "Ignored previews cannot be rebuilt" } });
  }

  const fullUser = preview.repoConfig.user;
  if (!fullUser.giteaInstanceUrl || !fullUser.giteaPAT) {
    return makeResponse({ ctr, content: { code: 400, message: "Gitea credentials not configured" } });
  }

  const repo = await fetchRepo(fullUser as any, preview.repoConfig.repoOwner, preview.repoConfig.repoName);
  const cloneUrl = repo.clone_url;
  if (!cloneUrl) {
    return makeResponse({ ctr, content: { code: 400, message: "Gitea repo clone URL not found" } });
  }

  const isFirstDeploy = preview.status === "STOPPED";
  if (isFirstDeploy) {
    await prisma.preview.update({
      where: { id: preview.id },
      data: { status: "PROVISIONING", stopReason: null, stoppedAt: null, lastActivityAt: new Date() },
    });
  } else {
    await prisma.preview.update({ where: { id: preview.id }, data: { lastActivityAt: new Date() } });
  }

  await prisma.job.create({
    data: {
      previewId: preview.id,
      type: "DEPLOY",
      status: "PENDING",
      payload: {
        commitSha: preview.commitSha,
        prNumber: preview.prNumber,
        prTitle: preview.prTitle,
        cloneUrl,
        isFirstDeploy,
      },
    },
  });

  return makeResponse({ ctr, content: { code: 200, message: "Rebuild job enqueued" } });
}

export async function previewLogsWs(ctr: any) {
  const auth = await resolveWsAuth(ctr);
  if (!auth?.success) {
    ctr.close(1008, "Unauthorized");
    return;
  }

  const id = parseInt(ctr.params.get("id") || "0", 10);
  const preview = await prisma.preview.findFirst({
    where: { id, repoConfig: { userId: auth.user.id } },
  });

  if (!preview) {
    ctr.close(1008, "Not found");
    return;
  }

  // rjweb's WS print signature is print(type, content) — content objects are
  // JSON-serialized for us. Passing a single stringified arg puts the JSON in
  // the `type` slot and sends an empty frame, so always use ("text", obj).
  await ctr.print("text", { type: "init", logs: preview.logs });

  const unsub = subscribeToLogs(id, (text) => {
    try {
      ctr.print("text", { type: "append", text });
    } catch {}
  });

  ctr.$abort(unsub);
}

// Live stream of the running app's OWN stdout/stderr (not the PP deploy log).
// Opens a dedicated SSH tail against the instance for the duration of the
// connection; nothing is persisted. Only available while the preview is live.
export async function previewAppLogsWs(ctr: any) {
  const auth = await resolveWsAuth(ctr);
  if (!auth?.success) {
    ctr.close(1008, "Unauthorized");
    return;
  }

  const id = parseInt(ctr.params.get("id") || "0", 10);
  const preview = await prisma.preview.findFirst({
    where: { id, repoConfig: { userId: auth.user.id } },
  });

  if (!preview) {
    ctr.close(1008, "Not found");
    return;
  }

  const stop = await streamAppLogs(id, (text) => {
    try {
      ctr.print("text", { type: "append", text });
    } catch {}
  });

  ctr.$abort(stop);
}
