import { randomUUID } from "node:crypto";
import {
  ADDRESS_CANDIDATES_POLL_MS,
  ADDRESS_CANDIDATES_RESULT_PREFIX,
  ADDRESS_CANDIDATES_RESULT_TTL_SECONDS,
} from "@/lib/constants/address-candidates-queue";
import { ADDRESS_CANDIDATES_TIMEOUT_MS } from "@/lib/constants/rpa";
import { ensureNativeInfraIfNeeded } from "@/lib/infra/ensure-native-infra";
import {
  ensureRedisReady,
  getRedis,
  resetRedisClient,
} from "@/lib/redis";
import { isParkedSessionEnabled } from "@/lib/rpa/env";
import { spawnFetchMothershipAddressCandidates } from "@/lib/rpa/spawn-address-candidates";
import { isAnyRpaWorkerHealthy } from "@/lib/rpa/worker-health";
import type {
  AddressLookupInput,
  MothershipCandidatesResult,
} from "@/modules/address/types";
import { RpaError, type RpaErrorCode } from "@/modules/rpa/errors";
import { addAddressCandidatesJob } from "@/workers/rpa/queue";
import type { AddressCandidatesJobData } from "@/modules/address/candidates-queue-types";

type StoredCandidatesSuccess = {
  ok: true;
  data: MothershipCandidatesResult;
};

type StoredCandidatesFailure = {
  ok: false;
  code: RpaErrorCode;
  message: string;
  retryable?: boolean;
};

type StoredCandidatesResult = StoredCandidatesSuccess | StoredCandidatesFailure;

function resultKey(jobId: string): string {
  return `${ADDRESS_CANDIDATES_RESULT_PREFIX}${jobId}`;
}

async function ensureCandidatesRedisReady(): Promise<void> {
  try {
    await ensureRedisReady(10, 800);
  } catch {
    ensureNativeInfraIfNeeded();
    resetRedisClient();
    await ensureRedisReady(15, 1_000);
  }
}

/** 快速探测是否可走 BullMQ（Redis + Worker 心跳均可用） */
async function canUseCandidatesQueue(): Promise<boolean> {
  try {
    await ensureRedisReady(5, 500);
    return await isAnyRpaWorkerHealthy();
  } catch {
    return false;
  }
}

export async function saveAddressCandidatesResult(
  jobId: string,
  payload: StoredCandidatesResult,
): Promise<void> {
  await ensureCandidatesRedisReady();
  await getRedis().setex(
    resultKey(jobId),
    ADDRESS_CANDIDATES_RESULT_TTL_SECONDS,
    JSON.stringify(payload),
  );
}

async function readAddressCandidatesResult(
  jobId: string,
): Promise<StoredCandidatesResult | null> {
  const raw = await getRedis().get(resultKey(jobId));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as StoredCandidatesResult;
}

async function requestViaSpawn(input: {
  pickup: AddressLookupInput;
  delivery: AddressLookupInput;
}): Promise<MothershipCandidatesResult> {
  return spawnFetchMothershipAddressCandidates(input);
}

/** 经 RPA Worker 执行地址候选；Worker/队列不可用时回退 spawn 子进程 */
export async function requestMothershipCandidatesViaWorker(input: {
  pickup: AddressLookupInput;
  delivery: AddressLookupInput;
}): Promise<MothershipCandidatesResult> {
  if (process.env.RPA_QUEUE_ENABLED === "false") {
    return requestViaSpawn(input);
  }

  const parkedSessionsEnabled = isParkedSessionEnabled();
  const useQueue = await canUseCandidatesQueue();
  if (!useQueue) {
    if (parkedSessionsEnabled) {
      throw new RpaError(
        "PAGE_LOAD_TIMEOUT",
        "RPA Worker 未运行，无法使用单会话加速。请执行 npm run worker:rpa 或 npm run dev:start",
        { retryable: true },
      );
    }
    console.warn(
      "[candidates] Redis/Worker 不可用，改用子进程执行地址联想",
    );
    return requestViaSpawn(input);
  }

  await ensureCandidatesRedisReady();

  const jobId = randomUUID();
  await addAddressCandidatesJob({
    jobId,
    pickup: input.pickup,
    delivery: input.delivery,
  });

  const deadline = Date.now() + ADDRESS_CANDIDATES_TIMEOUT_MS;
  while (Date.now() < deadline) {
    try {
      const stored = await readAddressCandidatesResult(jobId);
      if (stored?.ok) {
        return stored.data;
      }
      if (stored && !stored.ok) {
        throw new RpaError(stored.code, stored.message, {
          retryable: stored.retryable ?? false,
        });
      }
    } catch (error) {
      if (error instanceof RpaError) {
        throw error;
      }
      ensureNativeInfraIfNeeded();
      resetRedisClient();
      await ensureCandidatesRedisReady();
    }
    await new Promise((resolve) => setTimeout(resolve, ADDRESS_CANDIDATES_POLL_MS));
  }

  throw new RpaError(
    "PAGE_LOAD_TIMEOUT",
    "MotherShip 地址联想超时，请确认 RPA Worker 与 Redis 已启动（npm run dev:start）",
    { retryable: true },
  );
}
