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

import type {
  RawDbExpectConfig,
  ResolvedDbExpectConfig,
  ResolvedDbTarget,
} from "../../../../../src/server/checker/runner/db/types";
import type { CheckerContext } from "../../../../../src/server/checker/runner/types";

import { resolveContentExpectations } from "../../../../../src/server/checker/expect/content";
import { resolveKeyedExpectations } from "../../../../../src/server/checker/expect/keyed";
import { resolveValueExpectation } from "../../../../../src/server/checker/expect/value";
import { DbChecker } from "../../../../../src/server/checker/runner/db/execute";

const checker = new DbChecker();

function makeCtx(timeoutMs = 5000): CheckerContext {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), timeoutMs);
  return { signal: controller.signal };
}

function makeTarget(db: Partial<ResolvedDbTarget["db"]>, overrides?: { expect?: RawDbExpectConfig }): ResolvedDbTarget {
  const raw = overrides?.expect;
  const resolvedExpect: ResolvedDbExpectConfig | undefined = raw
    ? {
        durationMs: resolveValueExpectation(raw.durationMs),
        result: resolveContentExpectations(raw.result),
        rowCount: resolveValueExpectation(raw.rowCount),
        rows: raw.rows?.map((row) => resolveKeyedExpectations(row) ?? []),
      }
    : undefined;

  return {
    db: {
      url: "sqlite://:memory:",
      ...db,
    },
    description: null,
    expect: resolvedExpect,
    group: "default",
    id: "test-db",
    intervalMs: 60000,
    name: "test-db",
    rawExpect: raw,
    timeoutMs: 5000,
    type: "db",
  };
}

describe("DbChecker", () => {
  test("无 query 时仅测试连接成功", async () => {
    const result = await checker.execute(makeTarget({}), makeCtx());
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ connected: true });
    expect(result.failure).toBeNull();
  });

  test("执行查询成功", async () => {
    const result = await checker.execute(makeTarget({ query: "SELECT 1 as num, 'hello' as str" }), makeCtx());
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ connected: true, rowCount: 1 });
    expect(result.durationMs).toBeGreaterThanOrEqual(0);
  });

  test("查询返回多行", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1 as n UNION ALL SELECT 2 UNION ALL SELECT 3" }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ connected: true, rowCount: 3 });
  });

  test("查询返回空结果", async () => {
    const result = await checker.execute(makeTarget({ query: "SELECT 1 as n WHERE 1=0" }), makeCtx());
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ connected: true, rowCount: 0 });
  });

  test("连接失败返回 connect phase 错误", async () => {
    const result = await checker.execute(makeTarget({ url: "sqlite:///nonexistent/path/db.db" }), makeCtx());
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("connect");
    expect(result.failure!.message).toBeTruthy();
  });

  test("SQL 语法错误返回 query phase 错误", async () => {
    const result = await checker.execute(makeTarget({ query: "SELECT INVALID SQL" }), makeCtx());
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("query");
    expect(result.failure!.message).toBeTruthy();
  });

  test("durationMs 超时返回失败", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1" }, { expect: { durationMs: { lt: 0 } } }),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("duration");
  });

  test("rowCount 断言通过", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1 UNION ALL SELECT 2" }, { expect: { rowCount: { gte: 2 } } }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("rowCount 断言失败", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1 UNION ALL SELECT 2" }, { expect: { rowCount: { gte: 5 } } }),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("rowCount");
  });

  test("rows 断言通过（字面量形式）", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 100 as cnt" }, { expect: { rows: [{ cnt: 100 }] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("rows 断言通过（operator 形式）", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 150 as cnt" }, { expect: { rows: [{ cnt: { gte: 100 } }] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("rows 断言失败", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 50 as cnt" }, { expect: { rows: [{ cnt: { gte: 100 } }] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("row");
    expect(result.failure!.path).toBe("rows[0].cnt");
  });

  test("rows 结果行数不足", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1 as n" }, { expect: { rows: [{ n: 1 }, { n: 2 }] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("row");
    expect(result.failure!.message).toContain("行数不足");
  });

  test("rows 只检查声明的列", async () => {
    const result = await checker.execute(
      makeTarget({ query: "SELECT 1 as cnt, 'ignored' as other" }, { expect: { rows: [{ cnt: { gte: 1 } }] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("serialize 屏蔽凭据", () => {
    const target = makeTarget({ url: "postgres://user:pass@host:5432/db" });
    const s = checker.serialize(target);
    expect(s.target).toBe("postgres://***:***@host:5432/db");
    const config = JSON.parse(s.config) as { url: string };
    expect(config.url).toBe("postgres://***:***@host:5432/db");
  });

  test("serialize 无凭据的 url 保持原样", () => {
    const target = makeTarget({ url: "sqlite:///data/app.db" });
    const s = checker.serialize(target);
    expect(s.target).toBe("sqlite:///data/app.db");
  });
});
