import { SQL } from "bun";
import { isError } from "es-toolkit";

import type { CheckResult, RawTargetConfig } from "../../types";
import type { CheckerContext, CheckerDefinition, CheckerValidationInput, ResolveContext } from "../types";
import type { DbTargetConfig, RawDbExpectConfig, ResolvedDbExpectConfig, ResolvedDbTarget } from "./types";

import { checkContentExpectations, resolveContentExpectations } from "../../expect/content";
import { errorFailure } from "../../expect/failure";
import { resolveKeyedExpectations } from "../../expect/keyed";
import { checkValueExpectation, resolveValueExpectation } from "../../expect/value";
import { checkRowCount, checkRows } from "./expect";
import { dbCheckerSchemas } from "./schema";
import { validateDbConfig } from "./validate";

const PROBE_QUERY = "SELECT 1";
const ROWS_PREVIEW_MAX = 5;

export class DbChecker implements CheckerDefinition<ResolvedDbTarget> {
  readonly configKey = "db";

  readonly schemas = dbCheckerSchemas;

  readonly type = "db";

  buildDetail(observation: Record<string, unknown>): null | string {
    const connected = observation["connected"];
    if (connected !== true) {
      const error = observation["error"];
      return typeof error === "string" ? `connection failed: ${error}` : "not connected";
    }
    const rowCount = observation["rowCount"];
    if (typeof rowCount === "number") {
      return `${rowCount} rows`;
    }
    return "connected";
  }

  async execute(t: ResolvedDbTarget, ctx: CheckerContext): Promise<CheckResult> {
    const timestamp = new Date().toISOString();
    const start = performance.now();
    let db: SQL | undefined;

    try {
      db = new SQL(t.db.url);

      ctx.signal.addEventListener(
        "abort",
        () => {
          void db?.close({ timeout: 0 }).catch(() => {
            /* best-effort close */
          });
        },
        { once: true },
      );

      try {
        await db.unsafe(PROBE_QUERY);
      } catch (error) {
        const durationMs = Math.round(performance.now() - start);
        const errorMsg = isError(error) ? error.message : String(error);
        return {
          detail: null,
          durationMs,
          failure: errorFailure("connect", "connect", errorMsg),
          matched: false,
          observation: { connected: false, error: errorMsg, rowCount: null, rowsPreview: null },
          targetId: t.id,
          timestamp,
        };
      }

      if (!t.db.query) {
        const durationMs = Math.round(performance.now() - start);
        const observation: Record<string, unknown> = {
          connected: true,
          error: null,
          rowCount: null,
          rowsPreview: null,
        };
        const durationResult = checkValueExpectation(durationMs, t.expect?.durationMs, {
          message: "durationMs mismatch",
          path: "durationMs",
          phase: "duration",
        });
        if (!durationResult.matched) {
          return {
            detail: null,
            durationMs,
            failure: durationResult.failure,
            matched: false,
            observation,
            targetId: t.id,
            timestamp,
          };
        }
        return {
          detail: null,
          durationMs,
          failure: null,
          matched: true,
          observation,
          targetId: t.id,
          timestamp,
        };
      }

      let rows: unknown[];
      try {
        rows = await db.unsafe(t.db.query);
      } catch (error) {
        const durationMs = Math.round(performance.now() - start);
        const errorMsg = isError(error) ? error.message : String(error);
        return {
          detail: null,
          durationMs,
          failure: errorFailure("query", "query", errorMsg),
          matched: false,
          observation: { connected: true, error: errorMsg, rowCount: null, rowsPreview: null },
          targetId: t.id,
          timestamp,
        };
      }

      const durationMs = Math.round(performance.now() - start);
      const rowCount = Array.isArray(rows) ? rows.length : 0;
      const rowsPreview = Array.isArray(rows) ? rows.slice(0, ROWS_PREVIEW_MAX) : null;
      const observation: Record<string, unknown> = { connected: true, error: null, rowCount, rowsPreview };

      if (ctx.signal.aborted) {
        return {
          detail: null,
          durationMs,
          failure: errorFailure("query", "timeout", `查询超时 (${t.timeoutMs}ms)`),
          matched: false,
          observation,
          targetId: t.id,
          timestamp,
        };
      }

      const durationResult = checkValueExpectation(durationMs, t.expect?.durationMs, {
        message: "durationMs mismatch",
        path: "durationMs",
        phase: "duration",
      });
      if (!durationResult.matched) {
        return {
          detail: null,
          durationMs,
          failure: durationResult.failure,
          matched: false,
          observation,
          targetId: t.id,
          timestamp,
        };
      }

      if (t.expect?.rowCount) {
        const rowCountResult = checkRowCount(rowCount, t.expect.rowCount);
        if (!rowCountResult.matched) {
          return {
            detail: null,
            durationMs,
            failure: rowCountResult.failure,
            matched: false,
            observation,
            targetId: t.id,
            timestamp,
          };
        }
      }

      if (t.expect?.rows && t.expect.rows.length > 0) {
        const rowsResult = checkRows(rows, t.expect.rows);
        if (!rowsResult.matched) {
          return {
            detail: null,
            durationMs,
            failure: rowsResult.failure,
            matched: false,
            observation,
            targetId: t.id,
            timestamp,
          };
        }
      }

      if (t.expect?.result && t.expect.result.length > 0) {
        const resultCheck = checkContentExpectations({ rowCount, rows }, t.expect.result, {
          path: "result",
          phase: "result",
        });
        if (!resultCheck.matched) {
          return {
            detail: null,
            durationMs,
            failure: resultCheck.failure,
            matched: false,
            observation,
            targetId: t.id,
            timestamp,
          };
        }
      }

      return {
        detail: null,
        durationMs,
        failure: null,
        matched: true,
        observation,
        targetId: t.id,
        timestamp,
      };
    } finally {
      if (db) {
        try {
          await db.close({ timeout: 0 });
        } catch {
          /* best-effort close */
        }
      }
    }
  }

  resolve(target: RawTargetConfig, context: ResolveContext): ResolvedDbTarget {
    const t = target as RawTargetConfig & { db: DbTargetConfig; type: "db" };

    const rawExpect = target.expect as RawDbExpectConfig | undefined;
    const resolvedExpect: ResolvedDbExpectConfig | undefined = rawExpect
      ? {
          durationMs: resolveValueExpectation(rawExpect.durationMs),
          result: resolveContentExpectations(rawExpect.result),
          rowCount: resolveValueExpectation(rawExpect.rowCount),
          rows: rawExpect.rows?.map((r) => resolveKeyedExpectations(r)!),
        }
      : undefined;

    return {
      db: {
        query: t.db.query,
        url: t.db.url,
      },
      description: null,
      expect: resolvedExpect,
      group: target.group ?? "default",
      id: t.id,
      intervalMs: context.defaultIntervalMs,
      name: t.name ?? null,
      rawExpect,
      timeoutMs: context.defaultTimeoutMs,
      type: "db",
    } satisfies ResolvedDbTarget;
  }

  serialize(t: ResolvedDbTarget): { config: string; target: string } {
    // 屏蔽凭据：postgres://user:pass@host → postgres://***:***@host
    const masked = t.db.url.replace(/:\/\/([^@]+)@/, "://***:***@");
    return {
      config: JSON.stringify({
        query: t.db.query ?? null,
        url: masked,
      }),
      target: masked,
    };
  }

  validate(input: CheckerValidationInput) {
    return validateDbConfig(input);
  }
}
