import { type UniDeskConfig } from "./config";
import { coreInternalFetch } from "./microservices";

function stringOption(args: string[], name: string): string | undefined {
  const index = args.indexOf(name);
  if (index === -1) return undefined;
  const raw = args[index + 1];
  if (raw === undefined || raw.length === 0) throw new Error(`${name} requires a non-empty value`);
  return raw;
}

function numberOption(args: string[], name: string, defaultValue: number): number {
  const raw = stringOption(args, name);
  if (raw === undefined) return defaultValue;
  const value = Number(raw);
  if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
  return value;
}

function booleanOption(args: string[], name: string, defaultValue: boolean): boolean {
  const raw = stringOption(args, name);
  if (raw === undefined) return defaultValue;
  if (["1", "true", "yes", "on"].includes(raw.toLowerCase())) return true;
  if (["0", "false", "no", "off"].includes(raw.toLowerCase())) return false;
  throw new Error(`${name} must be true or false`);
}

function responseBody(response: unknown): Record<string, unknown> {
  if (typeof response !== "object" || response === null || Array.isArray(response)) return {};
  const body = (response as { body?: unknown }).body;
  return typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
}

function terminalRunStatus(status: unknown): boolean {
  return ["succeeded", "failed", "skipped"].includes(String(status || ""));
}

function isPlainNumeric(value: string): boolean {
  return /^[0-9]+$/.test(value);
}

export function scheduleRunsScope(args: string[]): { scheduleId: string | null; limit: number } {
  const limit = numberOption(args, "--limit", 50);
  let scheduleId: string | null = null;
  for (let index = 1; index < args.length; index += 1) {
    const arg = args[index];
    if (arg === "--limit") {
      index += 1;
      continue;
    }
    if (arg.startsWith("--")) {
      throw new Error(`unsupported schedule runs option: ${arg}`);
    }
    if (scheduleId !== null) {
      throw new Error("schedule runs accepts at most one schedule id");
    }
    if (isPlainNumeric(arg)) {
      throw new Error("schedule runs <scheduleId> requires a non-numeric schedule id; use schedule runs --limit N for the global history view");
    }
    scheduleId = arg;
  }
  return {
    scheduleId,
    limit,
  };
}

async function waitForScheduleRun(scheduleId: string, runId: string, timeoutMs: number): Promise<unknown> {
  const started = Date.now();
  let latest: unknown = null;
  const observeCommand = `bun scripts/cli.ts schedule runs ${scheduleId} --limit 20`;
  while (Date.now() - started < timeoutMs) {
    latest = coreInternalFetch(`/api/schedules/${encodeURIComponent(scheduleId)}/runs?limit=20`);
    const runs = responseBody(latest).runs;
    if (Array.isArray(runs)) {
      const run = runs.find((item) => typeof item === "object" && item !== null && (item as { id?: unknown }).id === runId);
      if (run !== undefined && terminalRunStatus((run as { status?: unknown }).status)) return { ok: true, run };
    }
    await Bun.sleep(2000);
  }
  return { ok: false, timedOut: true, timeoutMs, runId, scheduleId, observeCommand, latest };
}

export function scheduleRunObservation(scheduleId: string, response: unknown, wait: unknown): Record<string, unknown> {
  const run = responseBody(response).run as { id?: unknown } | undefined;
  const runId = typeof run?.id === "string" ? run.id : null;
  return {
    trigger: response,
    originalRunId: run?.id ?? null,
    scheduleId,
    newRunId: runId,
    observeCommand: `bun scripts/cli.ts schedule runs ${scheduleId} --limit 20`,
    wait,
  };
}

export function scheduleRetryRunObservation(originalRunId: string, response: unknown): Record<string, unknown> {
  const body = responseBody(response);
  const scheduleId = typeof body.scheduleId === "string" ? body.scheduleId : null;
  const newRunId = typeof body.newRunId === "string" ? body.newRunId : null;
  return {
    originalRunId: String(body.originalRunId ?? originalRunId),
    scheduleId,
    newRunId,
    observeCommand: scheduleId !== null && newRunId !== null ? `bun scripts/cli.ts schedule runs ${scheduleId} --limit 20` : null,
    response,
  };
}

function pgdataBackupScheduleBody(config: UniDeskConfig, args: string[]): Record<string, unknown> {
  const id = stringOption(args, "--id") ?? "unidesk-pgdata-baidu-daily";
  const timeOfDay = stringOption(args, "--time") ?? "03:30";
  const remoteBaseDir = stringOption(args, "--remote-base") ?? "/SERVER_DATA/UNIDESK_PG_DATA";
  const stagingSubdir = stringOption(args, "--staging-subdir") ?? "server-data/unidesk-pg-data";
  const enabled = args.includes("--disabled") ? false : booleanOption(args, "--enabled", true);
  const timeoutMs = numberOption(args, "--timeout-ms", 60 * 60_000);
  return {
    id,
    name: "PGDATA daily Baidu Netdisk backup",
    description: "Daily PostgreSQL physical base backup uploaded to Baidu Netdisk /SERVER_DATA with monthly rotation.",
    enabled,
    concurrencyPolicy: "skip",
    schedule: { type: "daily", timeOfDay, timezone: config.project.timezone || "Etc/UTC" },
    action: {
      type: "pgdata_backup",
      volumeName: config.database.volume,
      remoteBaseDir,
      stagingSubdir,
      baiduBaseUrl: "http://baidu-netdisk:4244",
      timeoutMs,
      cleanupLocal: true,
    },
  };
}

export async function runScheduleCommand(config: UniDeskConfig, args: string[]): Promise<unknown> {
  const [action = "list", idArg] = args;
  if (action === "list") return coreInternalFetch("/api/schedules?limit=200");
  if (action === "get") {
    if (!idArg) throw new Error("schedule get requires schedule id");
    return coreInternalFetch(`/api/schedules/${encodeURIComponent(idArg)}`);
  }
  if (action === "runs") {
    const { scheduleId, limit } = scheduleRunsScope(args);
    return scheduleId
      ? coreInternalFetch(`/api/schedules/${encodeURIComponent(scheduleId)}/runs?limit=${limit}`)
      : coreInternalFetch(`/api/schedules/runs?limit=${limit}`);
  }
  if (action === "delete") {
    if (!idArg) throw new Error("schedule delete requires schedule id");
    return coreInternalFetch(`/api/schedules/${encodeURIComponent(idArg)}`, { method: "DELETE" });
  }
  if (action === "run") {
    if (!idArg) throw new Error("schedule run requires schedule id");
    const response = coreInternalFetch(`/api/schedules/${encodeURIComponent(idArg)}/run`, { method: "POST", body: {} });
    const run = responseBody(response).run as { id?: unknown } | undefined;
    const waitMs = numberOption(args, "--wait-ms", 0);
    const wait = waitMs > 0 && typeof run?.id === "string" ? await waitForScheduleRun(idArg, run.id, waitMs) : null;
    return scheduleRunObservation(idArg, response, wait);
  }
  if (action === "retry-run") {
    if (!idArg) throw new Error("schedule retry-run requires failed run id");
    const response = coreInternalFetch(`/api/schedules/runs/${encodeURIComponent(idArg)}/retry`, { method: "POST", body: {} });
    return scheduleRetryRunObservation(idArg, response);
  }
  if (action === "upsert-pgdata-backup" || action === "pgdata-backup") {
    const body = pgdataBackupScheduleBody(config, args);
    const id = String(body.id || "unidesk-pgdata-baidu-daily");
    return coreInternalFetch(`/api/schedules/${encodeURIComponent(id)}`, { method: "PUT", body });
  }
  throw new Error("schedule command must be one of: list, get, runs, run, retry-run, delete, upsert-pgdata-backup");
}
