import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import type { ResolvedCommandTarget } from "../../../src/server/checker/runner/cmd/types";
import type { ResolvedHttpTarget } from "../../../src/server/checker/runner/http/types";
import type { CheckFailure } from "../../../src/server/checker/types";

import { checkerRegistry } from "../../../src/server/checker/runner";
import { CommandChecker } from "../../../src/server/checker/runner/cmd/execute";
import { HttpChecker } from "../../../src/server/checker/runner/http/execute";
import { ProbeStore } from "../../../src/server/checker/store";
import { rmRetry } from "../../helpers";

function ensureRegistered() {
  if (!checkerRegistry.supportedTypes.includes("http")) {
    checkerRegistry.register(new HttpChecker());
    checkerRegistry.register(new CommandChecker());
  }
}

beforeAll(() => {
  ensureRegistered();
});

const httpTarget: ResolvedHttpTarget = {
  description: null,
  expect: { body: [{ kind: "value", matcher: { contains: "ok" } }], durationMs: { equals: 3000 }, status: [200] },
  group: "default",
  http: {
    headers: { Accept: "application/json" },
    ignoreSSL: false,
    maxBodyBytes: 104857600,
    maxRedirects: 0,
    method: "GET",
    url: "https://example.com/health",
  },
  id: "test-http",
  intervalMs: 30000,
  name: "test-http",
  rawExpect: { body: [{ contains: "ok" }], durationMs: 3000 },
  timeoutMs: 10000,
  type: "http",
};

const commandTarget: ResolvedCommandTarget = {
  cmd: {
    args: ["-c", "1", "localhost"],
    cwd: "/tmp",
    env: {},
    exec: "ping",
    maxOutputBytes: 104857600,
  },
  description: null,
  group: "default",
  id: "test-cmd",
  intervalMs: 60000,
  name: "test-cmd",
  timeoutMs: 5000,
  type: "cmd",
};

describe("ProbeStore", () => {
  let tempDir: string;
  let store: ProbeStore;

  beforeAll(async () => {
    tempDir = join(tmpdir(), `gc-store-test-${Date.now()}`);
    await mkdir(tempDir, { recursive: true });
    store = new ProbeStore(join(tempDir, "test.db"));
  });

  afterAll(async () => {
    store.close();
    await rmRetry(tempDir);
  });

  test("初始化后无 targets", () => {
    expect(store.getTargets()).toHaveLength(0);
  });

  test("同步 http 和 cmd targets", () => {
    store.syncTargets([httpTarget, commandTarget]);
    const targets = store.getTargets();
    expect(targets).toHaveLength(2);
    expect(targets.map((target) => target.id).sort()).toEqual(["test-cmd", "test-http"]);
  });

  test("http target 字段正确", () => {
    const t = store.getTargets().find((t) => t.id === "test-http")!;
    expect(t.type).toBe("http");
    expect(t.target).toBe("https://example.com/health");
    const config = JSON.parse(t.config) as {
      headers: Record<string, string>;
      ignoreSSL: boolean;
      maxBodyBytes: number;
      maxRedirects: number;
      method: string;
      url: string;
    };
    expect(config.url).toBe("https://example.com/health");
    expect(config.method).toBe("GET");
    expect(config.headers).toEqual({ Accept: "application/json" });
    expect(config.ignoreSSL).toBe(false);
    expect(config.maxBodyBytes).toBe(104857600);
    expect(config.maxRedirects).toBe(0);
    expect(t.interval_ms).toBe(30000);
    expect(t.timeout_ms).toBe(10000);
    expect(JSON.parse(t.expect!)).toEqual({ body: [{ contains: "ok" }], durationMs: 3000 });
  });

  test("cmd target 字段正确", () => {
    const t = store.getTargets().find((t) => t.id === "test-cmd")!;
    expect(t.type).toBe("cmd");
    expect(t.target).toBe("exec ping -c 1 localhost");
    const config = JSON.parse(t.config) as { args: string[]; cwd: string; exec: string; maxOutputBytes: number };
    expect(config.exec).toBe("ping");
    expect(config.args).toEqual(["-c", "1", "localhost"]);
    expect(config.cwd).toBe("/tmp");
    expect(config.maxOutputBytes).toBe(104857600);
    expect(t.interval_ms).toBe(60000);
    expect(t.timeout_ms).toBe(5000);
    expect(t.expect).toBeNull();
  });

  test("同步更新已有 target", () => {
    const updated: ResolvedHttpTarget = {
      ...httpTarget,
      http: { ...httpTarget.http, url: "https://example.com/v2" },
    };
    store.syncTargets([updated, commandTarget]);
    const t = store.getTargets().find((t) => t.id === "test-http")!;
    expect(t.target).toBe("https://example.com/v2");
    expect(store.getTargets()).toHaveLength(2);
  });

  test("同步软删除 target", () => {
    store.syncTargets([httpTarget]);
    const targets = store.getTargets();
    expect(targets).toHaveLength(1);
    expect(targets[0]!.name).toBe("test-http");
  });

  test("重新同步回来", () => {
    store.syncTargets([httpTarget, commandTarget]);
    expect(store.getTargets()).toHaveLength(2);
  });

  test("getTargetById", () => {
    const found = store.getTargetById("test-http");
    expect(found).toBeDefined();
    expect(found!.name).toBe("test-http");
  });

  test("getTargetById 不存在", () => {
    expect(store.getTargetById("missing-target")).toBeNull();
  });

  test("写入 check result 并查询", () => {
    store.syncTargets([httpTarget, commandTarget]);

    store.insertCheckResult({
      durationMs: 150.5,
      failure: null,
      matched: true,
      observation: null,
      targetId: "test-http",
      timestamp: "2025-01-01T00:00:00.000Z",
    });

    store.insertCheckResult({
      durationMs: 300,
      failure: null,
      matched: true,
      observation: null,
      targetId: "test-http",
      timestamp: "2025-01-01T00:00:30.000Z",
    });

    const failure: CheckFailure = {
      actual: 5000,
      expected: 3000,
      kind: "error",
      message: "请求耗时 5000ms 超过限制 3000ms",
      path: "$.maxDurationMs",
      phase: "duration",
    };

    store.insertCheckResult({
      durationMs: null,
      failure,
      matched: false,
      observation: null,
      targetId: "test-http",
      timestamp: "2025-01-01T00:01:00.000Z",
    });

    const history = store.getHistory("test-http", "2025-01-01T00:00:00.000Z", "2025-12-31T23:59:59.999Z", 1, 10);
    expect(history.items).toHaveLength(3);
    expect(history.items[0]!.timestamp).toBe("2025-01-01T00:01:00.000Z");

    const latest = store.getLatestCheck("test-http")!;
    expect(latest.matched).toBe(0);
    expect(latest.failure).not.toBeNull();
    const parsedFailure = JSON.parse(latest.failure!) as CheckFailure;
    expect(parsedFailure.kind).toBe("error");
    expect(parsedFailure.phase).toBe("duration");
    expect(parsedFailure.message).toBe("请求耗时 5000ms 超过限制 3000ms");
  });

  test("getHistory 默认 limit=20", () => {
    for (let i = 0; i < 25; i++) {
      store.insertCheckResult({
        durationMs: 100 + i,
        failure: null,
        matched: true,
        observation: null,
        targetId: "test-http",
        timestamp: `2025-01-01T01:${String(i).padStart(2, "0")}:00.000Z`,
      });
    }

    const history = store.getHistory("test-http", "2025-01-01T00:00:00.000Z", "2025-12-31T23:59:59.999Z");
    expect(history.items).toHaveLength(20);
  });

  test("getTargetWindowStats 按时间窗口计算基础计数", () => {
    const stats = store.getTargetWindowStats("test-http", "2025-01-01T00:00:00.000Z", "2025-01-01T00:01:00.000Z");
    expect(stats.totalChecks).toBeGreaterThan(0);
    expect(stats.upChecks + stats.downChecks).toBe(stats.totalChecks);
    expect(stats.availability).toBeGreaterThanOrEqual(0);
    expect(stats.availability).toBeLessThanOrEqual(100);
  });

  test("无记录目标的窗口 stats", () => {
    const stats = store.getTargetWindowStats("test-cmd", "2025-01-01T00:00:00.000Z", "2025-01-01T00:01:00.000Z");
    expect(stats.totalChecks).toBe(0);
    expect(stats.upChecks).toBe(0);
    expect(stats.downChecks).toBe(0);
    expect(stats.availability).toBe(0);
  });

  test("getLatestChecksMap 支持 Dashboard 组装当前状态", () => {
    const latestChecksMap = store.getLatestChecksMap();
    const latest = latestChecksMap.get("test-http");

    expect(latest).toBeDefined();
    expect(latest!.timestamp).toBe("2025-01-01T01:24:00.000Z");
  });

  test("getTargetCheckpoints 返回窗口内升序检查点", () => {
    const checkpoints = store.getTargetCheckpoints("test-http", "2025-01-01T00:00:00.000Z", "2025-01-01T00:01:00.000Z");
    expect(checkpoints).toEqual([
      { duration_ms: 150.5, matched: 1, timestamp: "2025-01-01T00:00:00.000Z" },
      { duration_ms: 300, matched: 1, timestamp: "2025-01-01T00:00:30.000Z" },
      { duration_ms: null, matched: 0, timestamp: "2025-01-01T00:01:00.000Z" },
    ]);
  });

  test("getTargetDurations 返回成功检查耗时升序数组", () => {
    const durations = store.getTargetDurations("test-http", "2025-01-01T00:00:00.000Z", "2025-01-01T00:01:00.000Z");
    expect(durations).toEqual([150.5, 300]);
  });

  test("getRecentSamples 返回最近采样数据", () => {
    const samples = store.getRecentSamples("test-http", 10);
    expect(Array.isArray(samples)).toBe(true);
    expect(samples.length).toBeGreaterThan(0);
    for (const sample of samples) {
      expect(typeof sample.timestamp).toBe("string");
      expect(typeof sample.matched).toBe("number");
    }
  });

  test("getAllRecentSamples 返回每个 target 的最近采样数据", () => {
    const sampleStore = new ProbeStore(join(tempDir, "all-samples.db"));
    const httpA: ResolvedHttpTarget = { ...httpTarget, id: "sample-http-a", name: "sample-http-a" };
    const httpB: ResolvedHttpTarget = {
      ...httpTarget,
      http: { ...httpTarget.http, url: "https://example.com/other" },
      id: "sample-http-b",
      name: "sample-http-b",
    };
    const httpEmpty: ResolvedHttpTarget = {
      ...httpTarget,
      http: { ...httpTarget.http, url: "https://example.com/empty" },
      id: "sample-http-empty",
      name: "sample-http-empty",
    };
    sampleStore.syncTargets([httpA, httpB, httpEmpty]);
    const targets = sampleStore.getTargets();
    const targetAId = targets.find((t) => t.id === "sample-http-a")!.id;
    const targetBId = targets.find((t) => t.id === "sample-http-b")!.id;
    const emptyTargetId = targets.find((t) => t.id === "sample-http-empty")!.id;

    for (const [index, timestamp] of [
      "2025-01-01T00:00:00.000Z",
      "2025-01-01T00:01:00.000Z",
      "2025-01-01T00:02:00.000Z",
    ].entries()) {
      sampleStore.insertCheckResult({
        durationMs: 100 + index,
        failure: null,
        matched: index !== 1,
        observation: null,
        targetId: targetAId,
        timestamp,
      });
    }
    sampleStore.insertCheckResult({
      durationMs: 200,
      failure: null,
      matched: true,
      observation: null,
      targetId: targetBId,
      timestamp: "2025-01-01T00:03:00.000Z",
    });
    sampleStore.insertCheckResult({
      durationMs: null,
      failure: { kind: "error", message: "fail", path: "request", phase: "request" },
      matched: false,
      observation: null,
      targetId: targetBId,
      timestamp: "2025-01-01T00:04:00.000Z",
    });

    const samples = sampleStore.getAllRecentSamples(2);

    expect(samples.get(targetAId)).toEqual([
      { duration_ms: 102, matched: 1, timestamp: "2025-01-01T00:02:00.000Z" },
      { duration_ms: 101, matched: 0, timestamp: "2025-01-01T00:01:00.000Z" },
    ]);
    expect(samples.get(targetBId)).toEqual([
      { duration_ms: null, matched: 0, timestamp: "2025-01-01T00:04:00.000Z" },
      { duration_ms: 200, matched: 1, timestamp: "2025-01-01T00:03:00.000Z" },
    ]);
    expect(samples.has(emptyTargetId)).toBe(false);

    sampleStore.close();
  });

  test("关闭后操作不报错", () => {
    const closedStore = new ProbeStore(join(tempDir, "closed.db"));
    closedStore.close();
    expect(closedStore.getTargets()).toHaveLength(0);
    expect(closedStore.getTargetById("closed-target")).toBeNull();
  });

  test("移除 target 软删除保留 check_results", () => {
    const cascadeStore = new ProbeStore(join(tempDir, "cascade.db"));
    const cascadeTarget: ResolvedHttpTarget = {
      description: null,
      group: "default",
      http: {
        headers: {},
        ignoreSSL: false,
        maxBodyBytes: 104857600,
        maxRedirects: 0,
        method: "GET",
        url: "http://cascade.test",
      },
      id: "cascade-test",
      intervalMs: 30000,
      name: "cascade-test",
      timeoutMs: 10000,
      type: "http",
    };

    cascadeStore.syncTargets([cascadeTarget]);
    const t = cascadeStore.getTargets()[0]!;

    cascadeStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: "2025-01-01T00:00:00.000Z",
    });
    cascadeStore.insertCheckResult({
      durationMs: null,
      failure: { kind: "error", message: "fail", path: "$", phase: "status" },
      matched: false,
      observation: null,
      targetId: t.id,
      timestamp: "2025-01-01T00:01:00.000Z",
    });

    expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();

    cascadeStore.syncTargets([]);

    expect(cascadeStore.getTargets()).toHaveLength(0);
    expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();
    expect(cascadeStore.getHistory(t.id, "2000-01-01T00:00:00.000Z", "2099-12-31T23:59:59.999Z").total).toBe(2);

    cascadeStore.close();
  });

  test("getLatestChecksMap 返回所有 target 的最新 check", () => {
    const targets = store.getTargets();
    const map = store.getLatestChecksMap();
    expect(map).toBeInstanceOf(Map);

    for (const target of targets) {
      const latest = map.get(target.id);
      if (latest) {
        expect(latest.target_id).toBe(target.id);
      }
    }
  });

  test("getLatestChecksMap 对无记录的 target 不包含 key", () => {
    const freshStore = new ProbeStore(join(tempDir, "fresh-map.db"));
    freshStore.syncTargets([
      {
        description: null,
        group: "default",
        http: {
          headers: {},
          ignoreSSL: false,
          maxBodyBytes: 104857600,
          maxRedirects: 0,
          method: "GET",
          url: "http://no.records",
        },
        id: "no-records",
        intervalMs: 30000,
        name: "no-records",
        timeoutMs: 10000,
        type: "http",
      },
    ]);

    const map = freshStore.getLatestChecksMap();
    expect(map.size).toBe(0);

    freshStore.close();
  });

  test("getAllTargetWindowStats 返回所有 target 的窗口聚合统计", () => {
    const stats = store.getAllTargetWindowStats("2024-01-01T00:00:00.000Z", "2026-12-31T23:59:59.999Z");
    expect(stats).toBeInstanceOf(Map);

    const stats1 = stats.get("test-http");
    expect(stats1).toBeDefined();
    expect(stats1!.totalChecks).toBeGreaterThan(0);
    expect(stats1!.upChecks + stats1!.downChecks).toBe(stats1!.totalChecks);
    expect(stats1!.availability).toBeGreaterThanOrEqual(0);

    const stats2 = stats.get("test-cmd");
    if (stats2) {
      expect(stats2.totalChecks).toBe(0);
      expect(stats2.availability).toBe(0);
    }
  });

  test("getAllTargetWindowStats 对无记录的 target 不包含 key", () => {
    const freshStore = new ProbeStore(join(tempDir, "fresh-stats.db"));
    freshStore.syncTargets([
      {
        description: null,
        group: "default",
        http: {
          headers: {},
          ignoreSSL: false,
          maxBodyBytes: 104857600,
          maxRedirects: 0,
          method: "GET",
          url: "http://no.stats",
        },
        id: "no-stats",
        intervalMs: 30000,
        name: "no-stats",
        timeoutMs: 10000,
        type: "http",
      },
    ]);

    const stats = freshStore.getAllTargetWindowStats("2025-01-01T00:00:00.000Z", "2025-01-02T00:00:00.000Z");
    expect(stats.size).toBe(0);

    freshStore.close();
  });

  test("getAllTargetWindowStats 与 getTargetWindowStats 的 availability 精度一致", () => {
    const statsStore = new ProbeStore(join(tempDir, "stats-precision.db"));
    const target: ResolvedHttpTarget = { ...httpTarget, id: "stats-precision", name: "stats-precision" };
    statsStore.syncTargets([target]);
    const targetId = statsStore.getTargets()[0]!.id;

    for (const [index, matched] of [true, true, false].entries()) {
      statsStore.insertCheckResult({
        durationMs: 100,
        failure: null,
        matched,
        observation: null,
        targetId,
        timestamp: `2025-01-01T00:0${index}:00.000Z`,
      });
    }

    const targetStats = statsStore.getTargetWindowStats(
      targetId,
      "2025-01-01T00:00:00.000Z",
      "2025-01-01T00:02:00.000Z",
    );
    const allStats = statsStore
      .getAllTargetWindowStats("2025-01-01T00:00:00.000Z", "2025-01-01T00:02:00.000Z")
      .get(targetId)!;

    expect(targetStats.availability).toBe(66.67);
    expect(targetStats.upChecks).toBe(2);
    expect(targetStats.downChecks).toBe(1);
    expect(allStats.availability).toBe(66.67);
    expect(allStats.availability).toBe(targetStats.availability);

    statsStore.close();
  });

  test("getDashboardIncidentStates 返回按 target 和 timestamp 升序排列的状态序列", () => {
    const incidentStore = new ProbeStore(join(tempDir, "dashboard-incidents.db"));
    const httpA: ResolvedHttpTarget = { ...httpTarget, id: "incident-http-a", name: "incident-http-a" };
    const httpB: ResolvedHttpTarget = {
      ...httpTarget,
      http: { ...httpTarget.http, url: "https://example.com/incident-b" },
      id: "incident-http-b",
      name: "incident-http-b",
    };
    incidentStore.syncTargets([httpA, httpB]);
    const targets = incidentStore.getTargets();
    const targetAId = targets.find((target) => target.id === "incident-http-a")!.id;
    const targetBId = targets.find((target) => target.id === "incident-http-b")!.id;

    incidentStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: false,
      observation: null,
      targetId: targetBId,
      timestamp: "2025-01-01T00:03:00.000Z",
    });
    incidentStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: targetAId,
      timestamp: "2025-01-01T00:02:00.000Z",
    });
    incidentStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: false,
      observation: null,
      targetId: targetAId,
      timestamp: "2025-01-01T00:01:00.000Z",
    });

    expect(incidentStore.getDashboardIncidentStates("2025-01-01T00:00:00.000Z", "2025-01-01T00:03:00.000Z")).toEqual([
      { matched: 0, target_id: targetAId, timestamp: "2025-01-01T00:01:00.000Z" },
      { matched: 1, target_id: targetAId, timestamp: "2025-01-01T00:02:00.000Z" },
      { matched: 0, target_id: targetBId, timestamp: "2025-01-01T00:03:00.000Z" },
    ]);

    incidentStore.close();
  });

  test("prune 删除过期数据", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune.db"));
    pruneStore.syncTargets([httpTarget]);
    const t = pruneStore.getTargets()[0]!;

    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: "2020-01-01T00:00:00.000Z",
    });
    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: new Date().toISOString(),
    });

    const deleted = pruneStore.prune(86400000);
    expect(deleted).toBe(1);

    const history = pruneStore.getHistory(t.id, "2000-01-01T00:00:00.000Z", "2099-12-31T23:59:59.999Z");
    expect(history.total).toBe(1);

    pruneStore.close();
  });

  test("prune 无过期数据返回 0", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune-none.db"));
    pruneStore.syncTargets([httpTarget]);
    const t = pruneStore.getTargets()[0]!;

    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: new Date().toISOString(),
    });

    const deleted = pruneStore.prune(86400000);
    expect(deleted).toBe(0);

    pruneStore.close();
  });

  test("prune 不影响保留期内数据", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune-keep.db"));
    pruneStore.syncTargets([httpTarget]);
    const t = pruneStore.getTargets()[0]!;

    const now = Date.now();
    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: new Date(now - 3600000).toISOString(),
    });
    pruneStore.insertCheckResult({
      durationMs: 200,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: new Date(now).toISOString(),
    });

    const deleted = pruneStore.prune(7200000);
    expect(deleted).toBe(0);

    const history = pruneStore.getHistory(t.id, "2000-01-01T00:00:00.000Z", "2099-12-31T23:59:59.999Z");
    expect(history.total).toBe(2);

    pruneStore.close();
  });

  test("syncTargets 持久化 description", () => {
    const descStore = new ProbeStore(join(tempDir, "desc.db"));
    const targetWithDesc: ResolvedHttpTarget = {
      ...httpTarget,
      description: "检查 API 健康状态",
      id: "desc-test",
      name: "desc-test",
    };
    descStore.syncTargets([targetWithDesc]);
    const t = descStore.getTargets()[0]!;
    expect(t.description).toBe("检查 API 健康状态");
    descStore.close();
  });

  test("未配置 description 时持久化为 null", () => {
    const noDescStore = new ProbeStore(join(tempDir, "no-desc.db"));
    noDescStore.syncTargets([{ ...httpTarget, description: null, id: "no-desc", name: "no-desc" }]);
    const t = noDescStore.getTargets()[0]!;
    expect(t.description).toBeNull();
    noDescStore.close();
  });

  test("同步更新 description", () => {
    const updateDescStore = new ProbeStore(join(tempDir, "update-desc.db"));
    updateDescStore.syncTargets([{ ...httpTarget, description: "旧描述", id: "update-desc", name: "update-desc" }]);
    updateDescStore.syncTargets([{ ...httpTarget, description: "新描述", id: "update-desc", name: "update-desc" }]);
    const t = updateDescStore.getTargets()[0]!;
    expect(t.description).toBe("新描述");
    updateDescStore.close();
  });

  test("name 为 null 时持久化为 null", () => {
    const nullNameStore = new ProbeStore(join(tempDir, "null-name.db"));
    nullNameStore.syncTargets([{ ...httpTarget, id: "null-name", name: null }]);
    const t = nullNameStore.getTargets()[0]!;
    expect(t.name).toBeNull();
    nullNameStore.close();
  });

  test("targets 表 active 列默认值为 1", () => {
    const activeStore = new ProbeStore(join(tempDir, "active-default.db"));
    activeStore.syncTargets([{ ...httpTarget, id: "active-test", name: "active-test" }]);
    const t = activeStore.getTargets()[0]!;
    expect(t.active).toBe(1);
    activeStore.close();
  });

  test("check_results 外键约束为 RESTRICT", () => {
    const restrictStore = new ProbeStore(join(tempDir, "restrict.db"));
    restrictStore.syncTargets([{ ...httpTarget, id: "restrict-test", name: "restrict-test" }]);
    const t = restrictStore.getTargets()[0]!;
    restrictStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: t.id,
      timestamp: "2025-01-01T00:00:00.000Z",
    });
    expect(() => {
      restrictStore.deleteTargetRaw(t.id);
    }).toThrow();
    restrictStore.close();
  });

  test("syncTargets 新增目标 active=1", () => {
    const softStore = new ProbeStore(join(tempDir, "soft-insert.db"));
    softStore.syncTargets([{ ...httpTarget, id: "soft-a", name: "soft-a" }]);
    const t = softStore.getTargets()[0]!;
    expect(t.active).toBe(1);
    softStore.close();
  });

  test("syncTargets 移除目标设置 active=0", () => {
    const softStore = new ProbeStore(join(tempDir, "soft-remove.db"));
    softStore.syncTargets([
      { ...httpTarget, id: "soft-remove-a", name: "soft-remove-a" },
      { ...httpTarget, id: "soft-remove-b", name: "soft-remove-b" },
    ]);
    expect(softStore.getTargets()).toHaveLength(2);

    softStore.syncTargets([{ ...httpTarget, id: "soft-remove-a", name: "soft-remove-a" }]);
    expect(softStore.getTargets()).toHaveLength(1);
    expect(softStore.getTargets()[0]!.id).toBe("soft-remove-a");

    expect(softStore.getTargetActive("soft-remove-b")).toBe(0);

    softStore.close();
  });

  test("syncTargets 恢复已移除目标 active=1", () => {
    const restoreStore = new ProbeStore(join(tempDir, "soft-restore.db"));
    restoreStore.syncTargets([{ ...httpTarget, id: "restore-a", name: "restore-a" }]);

    restoreStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: "restore-a",
      timestamp: "2025-01-01T00:00:00.000Z",
    });

    restoreStore.syncTargets([]);
    expect(restoreStore.getTargets()).toHaveLength(0);

    restoreStore.syncTargets([{ ...httpTarget, id: "restore-a", name: "restore-a-updated" }]);
    expect(restoreStore.getTargets()).toHaveLength(1);
    expect(restoreStore.getTargets()[0]!.active).toBe(1);
    expect(restoreStore.getTargets()[0]!.name).toBe("restore-a-updated");

    const history = restoreStore.getHistory("restore-a", "2000-01-01T00:00:00.000Z", "2099-12-31T23:59:59.999Z");
    expect(history.total).toBe(1);

    restoreStore.close();
  });

  test("syncTargets 更新属性同时保持 active=1", () => {
    const updateStore = new ProbeStore(join(tempDir, "soft-update.db"));
    updateStore.syncTargets([{ ...httpTarget, id: "update-active", name: "old-name" }]);

    updateStore.syncTargets([{ ...httpTarget, id: "update-active", name: "new-name" }]);
    const t = updateStore.getTargets()[0]!;
    expect(t.name).toBe("new-name");
    expect(t.active).toBe(1);

    updateStore.close();
  });

  test("getTargets 不返回 inactive target", () => {
    const filterStore = new ProbeStore(join(tempDir, "filter-targets.db"));
    filterStore.syncTargets([
      { ...httpTarget, id: "filter-active", name: "filter-active" },
      { ...httpTarget, id: "filter-inactive", name: "filter-inactive" },
    ]);
    filterStore.syncTargets([{ ...httpTarget, id: "filter-active", name: "filter-active" }]);

    const targets = filterStore.getTargets();
    expect(targets).toHaveLength(1);
    expect(targets[0]!.id).toBe("filter-active");

    filterStore.close();
  });

  test("getTargetById 对 inactive target 返回 null", () => {
    const filterStore = new ProbeStore(join(tempDir, "filter-byid.db"));
    filterStore.syncTargets([{ ...httpTarget, id: "filter-id-test", name: "filter-id-test" }]);
    expect(filterStore.getTargetById("filter-id-test")).not.toBeNull();

    filterStore.syncTargets([]);
    expect(filterStore.getTargetById("filter-id-test")).toBeNull();

    filterStore.close();
  });

  test("prune 清理空壳 inactive target", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune-shell.db"));
    pruneStore.syncTargets([{ ...httpTarget, id: "prune-shell-target", name: "prune-shell-target" }]);

    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: "prune-shell-target",
      timestamp: "2020-01-01T00:00:00.000Z",
    });

    pruneStore.syncTargets([]);

    expect(pruneStore.getTargetActive("prune-shell-target")).toBe(0);

    pruneStore.prune(86400000);

    expect(pruneStore.hasTargetRow("prune-shell-target")).toBeFalse();

    pruneStore.close();
  });

  test("prune 保留有历史数据的 inactive target", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune-keep-inactive.db"));
    pruneStore.syncTargets([{ ...httpTarget, id: "prune-keep-target", name: "prune-keep-target" }]);

    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: "prune-keep-target",
      timestamp: "2020-01-01T00:00:00.000Z",
    });
    pruneStore.insertCheckResult({
      durationMs: 100,
      failure: null,
      matched: true,
      observation: null,
      targetId: "prune-keep-target",
      timestamp: new Date().toISOString(),
    });

    pruneStore.syncTargets([]);

    pruneStore.prune(86400000);

    expect(pruneStore.getTargetActive("prune-keep-target")).toBe(0);

    pruneStore.close();
  });

  test("prune 不清理无数据的 active target", () => {
    const pruneStore = new ProbeStore(join(tempDir, "prune-no-active.db"));
    pruneStore.syncTargets([{ ...httpTarget, id: "prune-active-target", name: "prune-active-target" }]);

    pruneStore.prune(86400000);

    expect(pruneStore.getTargetActive("prune-active-target")).toBe(1);

    pruneStore.close();
  });
});
