import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { StoreError } from "./article-store";

const ROOT = process.cwd();
const DATA_ROOT = process.env.CMS_DATA_DIR ? path.resolve(process.env.CMS_DATA_DIR) : path.join(ROOT, ".runtime");
const STORE_FILE = path.join(DATA_ROOT, "consultations", "store.json");
const SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1, maxmem: 32 * 1024 * 1024 };
const HASH_LENGTH = 64;

export type ConsultationStatus = "new" | "contacted" | "completed";

export interface ConsultationTeacher {
  id: string;
  name: string;
  username: string;
  title: string;
  wechat: string;
  qrCodeUrl: string;
  sort: number;
  enabled: boolean;
  assignedCount: number;
  lastAssignedAt: string | null;
  passwordSalt: string;
  passwordHash: string;
  sessionVersion: number;
  createdAt: string;
  updatedAt: string;
}

export interface ConsultationRecord {
  id: string;
  phone: string;
  teacherId: string;
  teacherName: string;
  teacherTitle: string;
  teacherWechat: string;
  teacherQrCodeUrl: string;
  source: string;
  page: string;
  status: ConsultationStatus;
  note: string;
  createdAt: string;
  updatedAt: string;
}

interface ConsultationState {
  version: 1;
  teachers: ConsultationTeacher[];
  consultations: ConsultationRecord[];
  lastAssignedTeacherId: string | null;
}

export type PublicTeacher = Pick<ConsultationTeacher, "id" | "name" | "title" | "wechat" | "qrCodeUrl">;
export type TeacherListItem = Omit<ConsultationTeacher, "passwordSalt" | "passwordHash">;

let mutationQueue: Promise<unknown> = Promise.resolve();

const emptyState = (): ConsultationState => ({ version: 1, teachers: [], consultations: [], lastAssignedTeacherId: null });

async function writeAtomic(file: string, content: string): Promise<void> {
  await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
  const temporary = `${file}.${process.pid}.${crypto.randomBytes(5).toString("hex")}.tmp`;
  try {
    await fs.writeFile(temporary, content, { mode: 0o600, flag: "wx" });
    await fs.rename(temporary, file);
  } catch (error) {
    await fs.rm(temporary, { force: true }).catch(() => {});
    throw error;
  }
}

async function readState(): Promise<ConsultationState> {
  try {
    const parsed = JSON.parse(await fs.readFile(STORE_FILE, "utf8")) as Partial<ConsultationState>;
    if (parsed.version !== 1 || !Array.isArray(parsed.teachers) || !Array.isArray(parsed.consultations)) {
      throw new Error("咨询数据文件格式无效");
    }
    return {
      version: 1,
      teachers: parsed.teachers as ConsultationTeacher[],
      consultations: parsed.consultations as ConsultationRecord[],
      lastAssignedTeacherId: typeof parsed.lastAssignedTeacherId === "string" ? parsed.lastAssignedTeacherId : null,
    };
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return emptyState();
    throw error;
  }
}

async function mutate<T>(operation: (state: ConsultationState) => Promise<T> | T): Promise<T> {
  const pending = mutationQueue.then(async () => {
    const state = await readState();
    const result = await operation(state);
    await writeAtomic(STORE_FILE, `${JSON.stringify(state, null, 2)}\n`);
    return result;
  });
  mutationQueue = pending.then(() => undefined, () => undefined);
  return pending;
}

function cleanText(value: unknown, label: string, maxLength: number, required = true): string {
  const text = String(value || "").replace(/\s+/g, " ").trim();
  if (required && !text) throw new StoreError(`请填写${label}`);
  if (text.length > maxLength) throw new StoreError(`${label}不能超过 ${maxLength} 个字`);
  return text;
}

function normalizeUsername(value: unknown): string {
  const username = String(value || "").trim().toLowerCase();
  if (!/^[a-z0-9_-]{3,32}$/.test(username)) throw new StoreError("登录账号需为 3-32 位字母、数字、下划线或短横线");
  return username;
}

function normalizeSort(value: unknown): number {
  const sort = Number(value);
  if (!Number.isInteger(sort) || sort < 1 || sort > 9999) throw new StoreError("轮询顺序需为 1-9999 的整数");
  return sort;
}

function normalizeQrCodeUrl(value: unknown): string {
  const url = String(value || "").trim();
  if (!/^\/uploads\/[a-zA-Z0-9._-]+$/.test(url)) throw new StoreError("请上传老师微信二维码");
  return url;
}

async function passwordHash(password: string, salt: Buffer): Promise<string> {
  return new Promise((resolve, reject) => {
    crypto.scrypt(password, salt, HASH_LENGTH, SCRYPT_OPTIONS, (error, key) => {
      if (error) reject(error);
      else resolve(key.toString("base64"));
    });
  });
}

async function passwordFields(value: unknown): Promise<{ passwordSalt: string; passwordHash: string }> {
  const password = String(value || "");
  if (password.length < 6 || password.length > 128) throw new StoreError("老师登录密码需为 6-128 个字符");
  const salt = crypto.randomBytes(16);
  return { passwordSalt: salt.toString("base64"), passwordHash: await passwordHash(password, salt) };
}

function publicTeacher(teacher: ConsultationTeacher): PublicTeacher {
  return { id: teacher.id, name: teacher.name, title: teacher.title, wechat: teacher.wechat, qrCodeUrl: teacher.qrCodeUrl };
}

function listItem(teacher: ConsultationTeacher): TeacherListItem {
  const { passwordSalt: _passwordSalt, passwordHash: _passwordHash, ...item } = teacher;
  return item;
}

export async function listConsultationTeachers(): Promise<TeacherListItem[]> {
  const state = await readState();
  return state.teachers.slice().sort((a, b) => a.sort - b.sort || a.createdAt.localeCompare(b.createdAt)).map(listItem);
}

export async function createConsultationTeacher(input: Record<string, unknown>): Promise<TeacherListItem> {
  return mutate(async (state) => {
    const username = normalizeUsername(input.username);
    if (state.teachers.some((teacher) => teacher.username === username)) throw new StoreError("该登录账号已被使用", 409);
    const now = new Date().toISOString();
    const teacher: ConsultationTeacher = {
      id: crypto.randomUUID(),
      name: cleanText(input.name, "老师姓名", 30),
      username,
      title: cleanText(input.title, "展示称谓", 40),
      wechat: cleanText(input.wechat, "微信号", 60, false),
      qrCodeUrl: normalizeQrCodeUrl(input.qrCodeUrl),
      sort: normalizeSort(input.sort),
      enabled: input.enabled !== false,
      assignedCount: 0,
      lastAssignedAt: null,
      ...(await passwordFields(input.password)),
      sessionVersion: 1,
      createdAt: now,
      updatedAt: now,
    };
    state.teachers.push(teacher);
    return listItem(teacher);
  });
}

export async function updateConsultationTeacher(id: string, input: Record<string, unknown>): Promise<TeacherListItem> {
  return mutate(async (state) => {
    const teacher = state.teachers.find((item) => item.id === id);
    if (!teacher) throw new StoreError("咨询老师不存在", 404);
    const username = normalizeUsername(input.username);
    if (state.teachers.some((item) => item.id !== id && item.username === username)) throw new StoreError("该登录账号已被使用", 409);
    teacher.name = cleanText(input.name, "老师姓名", 30);
    teacher.username = username;
    teacher.title = cleanText(input.title, "展示称谓", 40);
    teacher.wechat = cleanText(input.wechat, "微信号", 60, false);
    teacher.qrCodeUrl = normalizeQrCodeUrl(input.qrCodeUrl);
    teacher.sort = normalizeSort(input.sort);
    teacher.enabled = input.enabled !== false;
    if (input.password) {
      Object.assign(teacher, await passwordFields(input.password));
      teacher.sessionVersion += 1;
    }
    teacher.updatedAt = new Date().toISOString();
    return listItem(teacher);
  });
}

export async function deleteConsultationTeacher(id: string): Promise<void> {
  await mutate((state) => {
    const index = state.teachers.findIndex((teacher) => teacher.id === id);
    if (index < 0) throw new StoreError("咨询老师不存在", 404);
    state.teachers.splice(index, 1);
    if (state.lastAssignedTeacherId === id) state.lastAssignedTeacherId = null;
  });
}

export async function verifyTeacherCredentials(usernameValue: unknown, passwordValue: unknown): Promise<ConsultationTeacher | null> {
  const username = String(usernameValue || "").trim().toLowerCase();
  const password = String(passwordValue || "");
  if (!username || !password) return null;
  const state = await readState();
  const teacher = state.teachers.find((item) => item.username === username && item.enabled);
  if (!teacher) return null;
  const actual = await passwordHash(password, Buffer.from(teacher.passwordSalt, "base64"));
  const left = Buffer.from(actual);
  const right = Buffer.from(teacher.passwordHash);
  return left.length === right.length && crypto.timingSafeEqual(left, right) ? teacher : null;
}

export async function getTeacherSessionVersion(id: string): Promise<number | null> {
  const teacher = (await readState()).teachers.find((item) => item.id === id && item.enabled);
  return teacher?.sessionVersion ?? null;
}

export async function assignConsultation(input: Record<string, unknown>): Promise<{ consultation: ConsultationRecord; teacher: PublicTeacher }> {
  return mutate((state) => {
    const phone = String(input.phone || "").replace(/\s+/g, "");
    if (!/^1[3-9]\d{9}$/.test(phone)) throw new StoreError("请输入正确的 11 位手机号");
    const teachers = state.teachers.filter((teacher) => teacher.enabled).sort((a, b) => a.sort - b.sort || a.createdAt.localeCompare(b.createdAt));
    if (!teachers.length) throw new StoreError("咨询老师暂未配置，请稍后再试或拨打客服电话", 503);
    const previousIndex = teachers.findIndex((teacher) => teacher.id === state.lastAssignedTeacherId);
    const teacher = teachers[(previousIndex + 1) % teachers.length];
    const now = new Date().toISOString();
    const consultation: ConsultationRecord = {
      id: crypto.randomUUID(),
      phone,
      teacherId: teacher.id,
      teacherName: teacher.name,
      teacherTitle: teacher.title,
      teacherWechat: teacher.wechat,
      teacherQrCodeUrl: teacher.qrCodeUrl,
      source: cleanText(input.source, "来源", 80, false) || "官网学情分析",
      page: cleanText(input.page, "页面", 300, false),
      status: "new",
      note: "",
      createdAt: now,
      updatedAt: now,
    };
    state.consultations.unshift(consultation);
    state.lastAssignedTeacherId = teacher.id;
    teacher.assignedCount += 1;
    teacher.lastAssignedAt = now;
    teacher.updatedAt = now;
    return { consultation, teacher: publicTeacher(teacher) };
  });
}

export async function listConsultations(teacherId?: string): Promise<ConsultationRecord[]> {
  const records = (await readState()).consultations;
  return records.filter((item) => !teacherId || item.teacherId === teacherId).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}

export async function updateConsultation(id: string, input: Record<string, unknown>, teacherId?: string): Promise<ConsultationRecord> {
  return mutate((state) => {
    const consultation = state.consultations.find((item) => item.id === id && (!teacherId || item.teacherId === teacherId));
    if (!consultation) throw new StoreError("咨询记录不存在", 404);
    const status = String(input.status || consultation.status) as ConsultationStatus;
    if (!["new", "contacted", "completed"].includes(status)) throw new StoreError("咨询状态无效");
    consultation.status = status;
    consultation.note = cleanText(input.note, "跟进备注", 500, false);
    consultation.updatedAt = new Date().toISOString();
    return consultation;
  });
}
