import { describe, expect, test } from "bun:test";
import { join } from "node:path";

import type { ResolvedConfig } from "../../src/server/checker/config-loader";
import type { ProbeEngine } from "../../src/server/checker/engine";
import type { ProbeStore } from "../../src/server/checker/store";
import type { ResolvedTargetBase } from "../../src/server/checker/types";

import { bootstrap, type BootstrapDependencies } from "../../src/server/bootstrap";
import { createNoopLogger } from "../../src/server/logger";

type ShutdownSignal = "SIGINT" | "SIGTERM";

const target: ResolvedTargetBase = {
  description: null,
  group: "default",
  id: "test",
  intervalMs: 30000,
  name: "test",
  timeoutMs: 5000,
  type: "cmd",
};

function createHarness(overrides: BootstrapDependencies = {}) {
  const calls: string[] = [];
  const shutdownHandlers = new Map<ShutdownSignal, () => void>();
  const config: ResolvedConfig = {
    configDir: "/tmp",
    dataDir: "/tmp/dial-data",
    host: "127.0.0.1",
    logging: {
      consoleLevel: "info",
      fileLevel: "info",
      filePath: "/tmp/dial-data/logs/dial.log",
      rotationFrequency: "daily",
      rotationMaxFiles: 14,
      rotationSizeBytes: 52428800,
      rotationSizeRaw: "50MB",
    },
    maxConcurrentChecks: 3,
    port: 3000,
    retentionMs: 1000,
    targets: [target],
  };
  const store = {
    close() {
      calls.push("store.close");
    },
    syncTargets(targets: ResolvedTargetBase[]) {
      calls.push(`syncTargets:${targets.length}`);
    },
  } as unknown as ProbeStore;
  const engine = {
    start() {
      calls.push("engine.start");
    },
    stop() {
      calls.push("engine.stop");
    },
  } as unknown as ProbeEngine;

  const noopLogger = createNoopLogger();

  const dependencies: BootstrapDependencies = {
    createEngine(actualStore, targets, maxConcurrentChecks, retentionMs, logger) {
      expect(actualStore).toBe(store);
      expect(logger).toBe(noopLogger);
      calls.push(`createEngine:${targets.length}:${maxConcurrentChecks}:${retentionMs}`);
      return engine;
    },
    createLogger() {
      calls.push("createLogger");
      return Promise.resolve(noopLogger);
    },
    createStore(dbPath) {
      calls.push(`createStore:${dbPath}`);
      return store;
    },
    exit(code) {
      calls.push(`exit:${code}`);
      throw new Error(`exit:${code}`);
    },
    loadConfig(configPath) {
      calls.push(`loadConfig:${configPath}`);
      return Promise.resolve(config);
    },
    logError(...data) {
      calls.push(`logError:${String(data[1])}`);
    },
    onSignal(signal, handler) {
      calls.push(`onSignal:${signal}`);
      shutdownHandlers.set(signal, handler);
    },
    startServer(options) {
      expect(options.config).toEqual({ host: config.host, port: config.port });
      expect(options.store).toBe(store);
      expect(options.version).toBe("0.1.0");
      calls.push(`startServer:${options.mode}`);
    },
    ...overrides,
  };

  return { calls, dependencies, noopLogger, shutdownHandlers };
}

describe("bootstrap", () => {
  test("开发模式执行完整启动序列", async () => {
    const { calls, dependencies } = createHarness();

    await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development", version: "0.1.0" }, dependencies);

    expect(calls).toEqual([
      "loadConfig:/tmp/probes.yaml",
      "createLogger",
      `createStore:${join("/tmp/dial-data", "probe.db")}`,
      "syncTargets:1",
      "createEngine:1:3:1000",
      "engine.start",
      "onSignal:SIGINT",
      "onSignal:SIGTERM",
      "startServer:development",
    ]);
  });

  test("收到退出信号时停止 engine 并关闭 store", async () => {
    const { calls, dependencies, shutdownHandlers } = createHarness();

    await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development", version: "0.1.0" }, dependencies);

    expect(() => shutdownHandlers.get("SIGINT")!()).toThrow("exit:0");

    expect(calls.slice(-3)).toEqual(["engine.stop", "store.close", "exit:0"]);
  });

  test("启动失败时输出错误并以非零退出", async () => {
    const { calls, dependencies } = createHarness({
      loadConfig() {
        return Promise.reject(new Error("bad config"));
      },
    });

    let error: unknown;
    try {
      await bootstrap({ configPath: "/tmp/probes.yaml", mode: "development", version: "0.1.0" }, dependencies);
    } catch (caught) {
      error = caught;
    }

    expect(error).toBeInstanceOf(Error);
    expect((error as Error).message).toBe("exit:1");
    expect(calls).toEqual(["logError:bad config", "exit:1"]);
  });
});
