import { createHash, randomUUID } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import Redis from "ioredis";
import type { BrowserContext } from "playwright";
import {
  QUOTE_SESSION_REDIS_PREFIX,
  QUOTE_SESSION_TTL_SECONDS,
} from "@/lib/constants/quote-session";

export type QuoteSessionAddressInput = {
  street: string;
  city: string;
  state: string;
  zip: string;
};

export type QuoteSessionRecord = {
  sessionId: string;
  storagePath: string;
  createdAt: string;
  pickupKey: string;
  deliveryKey: string;
};

function getSessionDir(): string {
  return path.join(process.cwd(), ".rpa", "sessions");
}

function sessionFilePath(sessionId: string): string {
  return path.join(getSessionDir(), `${sessionId}.json`);
}

function redisKey(sessionId: string): string {
  return `${QUOTE_SESSION_REDIS_PREFIX}${sessionId}`;
}

function addressLookupKey(address: QuoteSessionAddressInput): string {
  return [address.street, address.city, address.state, address.zip]
    .map((part) => part.trim().toLowerCase())
    .join("|");
}

async function writeQuoteSessionToRedis(
  sessionId: string,
  record: QuoteSessionRecord,
): Promise<void> {
  const url = process.env.REDIS_URL?.trim();
  if (!url) {
    return;
  }

  const client = new Redis(url, {
    family: 4,
    connectTimeout: 3_000,
    maxRetriesPerRequest: 1,
    lazyConnect: true,
    retryStrategy: () => null,
  });

  try {
    await client.connect();
    await client.setex(
      redisKey(sessionId),
      QUOTE_SESSION_TTL_SECONDS,
      JSON.stringify(record),
    );
  } finally {
    await client.quit().catch(() => undefined);
  }
}

async function readQuoteSessionFromRedis(
  sessionId: string,
): Promise<QuoteSessionRecord | null> {
  const url = process.env.REDIS_URL?.trim();
  if (!url) {
    return null;
  }

  const client = new Redis(url, {
    family: 4,
    connectTimeout: 3_000,
    maxRetriesPerRequest: 1,
    lazyConnect: true,
    retryStrategy: () => null,
  });

  try {
    await client.connect();
    const raw = await client.get(redisKey(sessionId));
    return raw ? (JSON.parse(raw) as QuoteSessionRecord) : null;
  } finally {
    await client.quit().catch(() => undefined);
  }
}

/** 候选 RPA 结束后持久化 browser storageState，供询价 job 复用 */
export async function saveQuoteSessionFromContext(
  context: BrowserContext,
  input: {
    pickup: QuoteSessionAddressInput;
    delivery: QuoteSessionAddressInput;
  },
): Promise<string> {
  fs.mkdirSync(getSessionDir(), { recursive: true });
  const sessionId = randomUUID();
  const storagePath = sessionFilePath(sessionId);
  await context.storageState({ path: storagePath });

  const record: QuoteSessionRecord = {
    sessionId,
    storagePath,
    createdAt: new Date().toISOString(),
    pickupKey: addressLookupKey(input.pickup),
    deliveryKey: addressLookupKey(input.delivery),
  };

  try {
    await writeQuoteSessionToRedis(sessionId, record);
  } catch (error) {
    console.warn(
      "[quote-session] Redis 写入失败，仍保留本地会话文件",
      error instanceof Error ? error.message : error,
    );
  }

  console.log(`[quote-session] 已保存 session=${sessionId}`);
  return sessionId;
}

/** 询价 job 解析会话 storageState 路径（Redis 不可用时回退本地文件） */
export async function resolveQuoteSessionStoragePath(
  sessionId: string | undefined,
): Promise<string | null> {
  const id = sessionId?.trim();
  if (!id) {
    return null;
  }

  try {
    const record = await readQuoteSessionFromRedis(id);
    if (record && fs.existsSync(record.storagePath)) {
      return record.storagePath;
    }
  } catch {
    /* 回退文件 */
  }

  const fallback = sessionFilePath(id);
  return fs.existsSync(fallback) ? fallback : null;
}

export function hashQuoteSessionId(sessionId: string): string {
  return createHash("sha256").update(sessionId).digest("hex").slice(0, 12);
}
