import { prisma } from "@/lib/db";
import { eventBus } from "@/lib/events/bus";
import { runRemote, putFile, type SSHTarget } from "@/lib/ssh/client";
import { decrypt } from "@/lib/crypto/aes";
import { writeFile, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import type { DeployJob } from "@/lib/queue";

export async function runDeploy(data: DeployJob): Promise<void> {
  const dep = await prisma.deployHistory.findUnique({
    where: { id: data.deployId },
    include: { project: true },
  });
  if (!dep) throw new Error("deploy not found");
  const target = await prisma.deployTarget.findUnique({ where: { id: data.targetId } });
  if (!target) throw new Error("target not found");

  await prisma.deployHistory.update({
    where: { id: dep.id },
    data: { status: "deploying" },
  });
  eventBus.emit({ type: "deploy.update", deployId: dep.id, status: "deploying" });

  const composePathFinal = target.composePath.replace("{name}", dep.project.name);
  const composeDir = dirname(composePathFinal);

  // Resolve compose YAML
  let composeYaml: string;
  if (dep.project.composeSource === "inline" && dep.project.composeInline) {
    composeYaml = dep.project.composeInline;
  } else {
    composeYaml = `services:\n  app:\n    image: ${data.imageTag}\n    restart: unless-stopped\n    network_mode: host\n    env_file:\n      - .env\n`;
  }
  // Substitute ${IMAGE_TAG} placeholder so inline templates can stay version-agnostic
  composeYaml = composeYaml.replaceAll("${IMAGE_TAG}", data.imageTag);

  // Resolve env vars
  let envContent = "";
  if (dep.project.envVarsEncrypted) {
    const kv = JSON.parse(decrypt(dep.project.envVarsEncrypted)) as Record<string, string>;
    envContent = Object.entries(kv).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
  }

  // Stage locally
  const tmp = await mkdtemp(join(tmpdir(), "pm-deploy-"));
  await writeFile(join(tmp, "docker-compose.yml"), composeYaml);
  await writeFile(join(tmp, ".env"), envContent);

  const tCfg: SSHTarget = {
    host: target.host,
    sshPort: target.sshPort,
    sshUser: target.sshUser,
    authMode: target.authMode as "password" | "key",
    authBlob: target.authBlob,
  };

  const sink = (line: string) => eventBus.emit({ type: "deploy.log", deployId: dep.id, line });

  await runRemote(tCfg, `mkdir -p ${composeDir}`, sink);
  await putFile(tCfg, join(tmp, "docker-compose.yml"), `${composeDir}/docker-compose.yml`);
  await putFile(tCfg, join(tmp, ".env"), `${composeDir}/.env`);
  await runRemote(tCfg, `chmod 600 ${composeDir}/.env`, sink);

  const composeUp = await runRemote(
    tCfg,
    `cd ${composeDir} && docker compose pull && docker compose up -d`,
    sink,
  );

  if (composeUp.code !== 0) {
    await prisma.deployHistory.update({
      where: { id: dep.id },
      data: {
        status: "failed",
        finishedAt: new Date(),
        errorMsg: "compose_up_failed",
        logBlob: (dep.logBlob ?? "") + "\n--- DEPLOY ---\n" + composeUp.stderr.slice(-32000),
      },
    });
    eventBus.emit({ type: "deploy.update", deployId: dep.id, status: "failed" });
    return;
  }

  await prisma.deployHistory.update({
    where: { id: dep.id },
    data: {
      status: "success",
      finishedAt: new Date(),
      logBlob: (dep.logBlob ?? "") + "\n--- DEPLOY ---\n" + composeUp.stdout.slice(-32000),
    },
  });
  await prisma.project.update({
    where: { id: dep.projectId },
    data: { deployedVersion: dep.project.version, deployedAt: new Date() },
  });
  eventBus.emit({ type: "deploy.update", deployId: dep.id, status: "success" });
}
