import { prisma } from "../../lib/db";
import { makeResponse } from "../../lib/response";
import { ERROR_MESSAGES } from "../../lib/errors";
import { fetchUserRepos, registerWebhook, deleteWebhook } from "../../services/gitea";
import { getAdminSettings } from "../../lib/adminSettings";
import { env } from "../../lib/env";
import { computePreviewCostUsd } from "../../lib/cost";
import { normalizeInstanceType } from "../../lib/instanceTypes";
import { normalizeAptPackages, normalizeNodeVersion, normalizePreinstallTools } from "../../lib/preinstall";

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

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

  const fullUser = await prisma.user.findUnique({ where: { id: user.id } });
  if (!fullUser?.giteaInstanceUrl || !fullUser?.giteaPAT) {
    return makeResponse({ ctr, content: { code: 400, message: "Gitea credentials not configured" } });
  }

  const giteaRepos = await fetchUserRepos(fullUser as any);
  const configs = await prisma.repoConfig.findMany({ where: { userId: user.id } });
  const configMap = new Map(configs.map(c => [`${c.repoOwner}/${c.repoName}`, c]));

  // Estimated total EC2 cost per repo — sum over that repo's previews. See lib/cost.ts.
  const costPreviews = await prisma.preview.findMany({
    where: { repoConfig: { userId: user.id } },
    select: { repoConfigId: true, accumulatedCostUsd: true, instanceLaunchedAt: true, instanceType: true },
  });
  const costByConfigId = new Map<number, number>();
  for (const p of costPreviews) {
    costByConfigId.set(p.repoConfigId, (costByConfigId.get(p.repoConfigId) ?? 0) + computePreviewCostUsd(p));
  }

  const allOwners = [...new Set(giteaRepos.map((r: any) => r.full_name?.split("/")[0]).filter(Boolean))];
  const allConfigs = await prisma.repoConfig.findMany({
    where: { repoOwner: { in: allOwners } },
    select: { repoOwner: true, repoName: true, userId: true },
  });
  const claimedByOthers = new Set(
    allConfigs.filter(c => c.userId !== user.id).map(c => `${c.repoOwner}/${c.repoName}`)
  );

  const result = giteaRepos.map((r: any) => {
    const [owner, name] = (r.full_name || "").split("/");
    const key = `${owner}/${name}`;
    const config = configMap.get(key);
    const { giteaWebhookId, ...safeConfig } = config || {} as any;
    return {
      owner,
      name,
      fullName: r.full_name,
      htmlUrl: r.html_url,
      isPrivate: Boolean(r.private),
      isEnabled: config?.isEnabled ?? false,
      claimedByOther: claimedByOthers.has(key),
      config: config ? safeConfig : null,
      costUsd: config ? (costByConfigId.get(config.id) ?? 0) : 0,
    };
  }).sort((a: any, b: any) => {
    const configuredDelta = Number(Boolean(b.config)) - Number(Boolean(a.config));
    if (configuredDelta !== 0) return configuredDelta;

    const ownerDelta = a.owner.localeCompare(b.owner, undefined, { sensitivity: "base", numeric: true });
    if (ownerDelta !== 0) return ownerDelta;

    return a.name.localeCompare(b.name, undefined, { sensitivity: "base", numeric: true });
  });

  return makeResponse({ ctr, content: { code: 200, data: result } });
}

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

  const owner = ctr.params.get("owner");
  const repo = ctr.params.get("repo");

  const config = await prisma.repoConfig.findFirst({
    where: { repoOwner: owner, repoName: repo, userId: user.id },
  });
  if (!config) return makeResponse({ ctr, content: { code: 404, message: "Repo config not found" } });

  const { giteaWebhookId, ...safeConfig } = config;
  return makeResponse({ ctr, content: { code: 200, data: safeConfig } });
}

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

  const body = await ctr.body();
  const { owner, repo, ...configData } = body || {};
  if (!owner || !repo) return makeResponse({ ctr, content: { code: 400, message: "owner and repo required" } });

  const existing = await prisma.repoConfig.findFirst({
    where: { repoOwner: owner, repoName: repo },
  });

  if (existing && existing.userId !== user.id) {
    return makeResponse({ ctr, content: { code: 409, message: "This repo is already configured by another user." } });
  }

  const settings = await getAdminSettings();

  const useDockerCompose = Boolean(configData.useDockerCompose ?? false);
  const preinstallTools = normalizePreinstallTools(configData.preinstallTools, useDockerCompose);
  const sanitized = {
    repoOwner: owner,
    repoName: repo,
    userId: user.id,
    instanceType: normalizeInstanceType(configData.instanceType ?? settings.defaultInstanceType),
    inactivityHours: Math.min(72, Math.max(0.5, Number(configData.inactivityHours ?? 12))),
    port: Number(configData.port ?? 3000),
    envVars: configData.envVars ?? {},
    preinstallTools,
    nodeVersion: normalizeNodeVersion(configData.nodeVersion, preinstallTools.includes("node")),
    useDockerCompose,
    composeFilePath: configData.composeFilePath ?? null,
    aptPackages: normalizeAptPackages(configData.aptPackages),
    setupCommands: Array.isArray(configData.setupCommands) ? configData.setupCommands : [],
    buildCommands: Array.isArray(configData.buildCommands) ? configData.buildCommands : [],
    postBuildCommands: Array.isArray(configData.postBuildCommands) ? configData.postBuildCommands : [],
    runCommand: configData.runCommand ?? null,
    denyList: Array.isArray(configData.denyList) ? configData.denyList : [],
    disabledCommands: Array.isArray(configData.disabledCommands)
      ? configData.disabledCommands.filter((c: unknown): c is string => typeof c === "string" && c !== "help")
      : [],
  };

  let config;
  if (existing) {
    config = await prisma.repoConfig.update({ where: { id: existing.id }, data: sanitized });
  } else {
    config = await prisma.repoConfig.create({ data: sanitized });
  }

  const { giteaWebhookId, ...safeConfig } = config;
  return makeResponse({ ctr, content: { code: 200, data: safeConfig } });
}

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

  const body = await ctr.body();
  const { owner, repo, enabled } = body || {};
  if (!owner || !repo || enabled === undefined) {
    return makeResponse({ ctr, content: { code: 400, message: "owner, repo, enabled required" } });
  }

  const fullUser = await prisma.user.findUnique({ where: { id: user.id } });
  if (!fullUser?.giteaInstanceUrl || !fullUser?.giteaPAT) {
    return makeResponse({ ctr, content: { code: 400, message: "Gitea credentials not configured" } });
  }

  let config = await prisma.repoConfig.findFirst({ where: { repoOwner: owner, repoName: repo } });
  if (config && config.userId !== user.id) {
    return makeResponse({ ctr, content: { code: 409, message: "This repo is already configured by another user." } });
  }

  const webhookToken = await prisma.webhookToken.findUnique({ where: { userId: user.id } });
  if (!webhookToken) {
    return makeResponse({ ctr, content: { code: 400, message: "No webhook token configured. Go to Settings to generate one." } });
  }

  if (enabled) {
    if (!config) {
      config = await prisma.repoConfig.create({
        data: { repoOwner: owner, repoName: repo, userId: user.id, isEnabled: false },
      });
    }

    const webhookUrl = `${env.PP_BASE_URL}/webhook/${user.id}`;
    const hookId = await registerWebhook(fullUser as any, owner, repo, webhookUrl, webhookToken.token);
    await prisma.repoConfig.update({
      where: { id: config.id },
      data: { isEnabled: true, giteaWebhookId: String(hookId) },
    });
    return makeResponse({ ctr, content: { code: 200, message: "Repo enabled and webhook registered" } });
  } else {
    if (!config) return makeResponse({ ctr, content: { code: 404, message: "Repo config not found" } });
    if (config.giteaWebhookId) {
      try {
        await deleteWebhook(fullUser as any, owner, repo, config.giteaWebhookId);
      } catch (e: any) {
        return makeResponse({ ctr, content: { code: 400, message: `Failed to delete Gitea webhook: ${e.message}` } });
      }
    }
    await prisma.repoConfig.update({
      where: { id: config.id },
      data: { isEnabled: false, giteaWebhookId: null },
    });
    return makeResponse({ ctr, content: { code: 200, message: "Repo disabled and webhook removed" } });
  }
}
