import { describe, it, expect, beforeEach, afterEach } from "vitest";
import fs from "fs";
import os from "os";
import path from "path";
import {
  deployApp, removeApp, stopApp, restartApp, execInApp,
  getAppLogs, listAppVolumes, removeAppVolume
} from "../src/modules/apps.js";

let tmpDir: string;

function readMessages(tmpDir: string) {
  const messagesDir = path.join(tmpDir, "messages");
  if (!fs.existsSync(messagesDir)) return [];
  return fs.readdirSync(messagesDir)
    .filter((f) => f.endsWith(".json"))
    .map((f) => JSON.parse(fs.readFileSync(path.join(messagesDir, f), "utf8")));
}

beforeEach(() => {
  tmpDir = path.join(os.tmpdir(), `treehouse-test-${Math.random().toString(36).slice(2)}`);
  fs.mkdirSync(tmpDir, { recursive: true });
  process.env.TENANT_ID = "test-tenant";
  process.env.NANOCLAW_GROUP_FOLDER = "test/group";
});

afterEach(() => {
  fs.rmSync(tmpDir, { recursive: true, force: true });
  delete process.env.TENANT_ID;
  delete process.env.NANOCLAW_GROUP_FOLDER;
});

describe("deployApp", () => {
  it("writes app-apply IPC message", async () => {
    await deployApp("myapp", tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatchObject({ type: "app-apply", app: "myapp", groupFolder: "test/group" });
  });

  it("writes app-apply IPC message with force_rebuild", async () => {
    await deployApp("myapp", { ipcDir: tmpDir, forceRebuild: true });
    const msgs = readMessages(tmpDir);
    expect(msgs).toHaveLength(1);
    expect(msgs[0]).toMatchObject({
      type: "app-apply",
      app: "myapp",
      groupFolder: "test/group",
      force_rebuild: true,
    });
  });
});

describe("removeApp", () => {
  it("writes app-remove IPC message", async () => {
    await removeApp("myapp", tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-remove", app: "myapp", groupFolder: "test/group" });
  });
});

describe("stopApp", () => {
  it("writes compose-stop IPC message", async () => {
    await stopApp("myapp", tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "compose-stop", app: "myapp", groupFolder: "test/group" });
  });
});

describe("restartApp", () => {
  it("writes app-restart IPC message with services", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    const statusFile = path.join(statusDir, "myapp.json");
    // Write result after short delay (simulates IPC handler response)
    setTimeout(() => {
      fs.writeFileSync(statusFile, JSON.stringify({ stage: "restarted", updatedAt: "2026-01-01T00:00:01Z" }));
    }, 100);
    await restartApp("myapp", ["api", "worker"], tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-restart", name: "myapp", services: ["api", "worker"], groupFolder: "test/group" });
  });

  it("writes app-restart without services", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    const statusFile = path.join(statusDir, "myapp.json");
    setTimeout(() => {
      fs.writeFileSync(statusFile, JSON.stringify({ stage: "restarted", updatedAt: "2026-01-01T00:00:01Z" }));
    }, 100);
    await restartApp("myapp", undefined, tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-restart", name: "myapp", groupFolder: "test/group" });
  });
});

describe("execInApp", () => {
  it("writes app-exec IPC message", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    // Write result after clearStaleResult runs (after a short delay)
    setTimeout(() => {
      fs.writeFileSync(path.join(statusDir, "myapp-exec.json"), JSON.stringify({ ok: true, stdout: "result" }));
    }, 100);
    await execInApp("myapp", "api", ["ls", "-la"], tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-exec", app: "myapp", service: "api", command: ["ls", "-la"], groupFolder: "test/group" });
  });
});

describe("getAppLogs", () => {
  it("writes app-logs IPC message with tail", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    setTimeout(() => {
      fs.writeFileSync(path.join(statusDir, "myapp-logs.json"), JSON.stringify({ ok: true, logs: "log output" }));
    }, 100);
    await getAppLogs("myapp", "api", 100, tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-logs", app: "myapp", service: "api", tail: 100, groupFolder: "test/group" });
  });

  it("writes app-logs IPC message without tail", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    setTimeout(() => {
      fs.writeFileSync(path.join(statusDir, "myapp-logs.json"), JSON.stringify({ ok: true, logs: "log output" }));
    }, 100);
    await getAppLogs("myapp", "api", undefined, tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-logs", app: "myapp", service: "api", groupFolder: "test/group" });
  });
});

describe("listAppVolumes", () => {
  it("writes app-volume-list IPC message", async () => {
    const statusDir = path.join(tmpDir, "hosting-status");
    fs.mkdirSync(statusDir, { recursive: true });
    setTimeout(() => {
      fs.writeFileSync(path.join(statusDir, "myapp-volumes.json"), JSON.stringify({ ok: true, volumes: [] }));
    }, 100);
    await listAppVolumes("myapp", tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-volume-list", app: "myapp", groupFolder: "test/group" });
  });
});

describe("removeAppVolume", () => {
  it("writes app-volume-remove IPC message", async () => {
    await removeAppVolume("myapp", "data-vol", tmpDir);
    const msgs = readMessages(tmpDir);
    expect(msgs[0]).toMatchObject({ type: "app-volume-remove", app: "myapp", volume: "data-vol", groupFolder: "test/group" });
  });
});
