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 = "unprocessed" | "not_added" | "added";
export type StaffRole = "customer_service" | "operator";

export interface ConsultationTeacher {
  id: string;
  role: StaffRole;
  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;
  sequence: number;
  name: string;
  phone: string;
  location: string;
  teacherId: string;
  teacherName: string;
  teacherTitle: string;
  teacherWechat: string;
  teacherQrCodeUrl: string;
  source: string;
  page: string;
  status: ConsultationStatus;
  unaddedReason: string;
  callCount: number;
  note: string;
  createdAt: string;
  updatedAt: string;
}

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

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, nextConsultationNumber: 1 });

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("咨询数据文件格式无效");
    }
    const rawConsultations = parsed.consultations as Array<Partial<ConsultationRecord> & { status?: string }>;
    const total = rawConsultations.length;
    const consultations = rawConsultations.map((item, index) => {
      const legacyStatus = String(item.status || "");
      return {
        ...item,
        sequence: Number.isInteger(item.sequence) ? Number(item.sequence) : total - index,
        name: String(item.name || ""),
        location: String(item.location || ""),
        status: legacyStatus === "added" || legacyStatus === "completed"
          ? "added"
          : legacyStatus === "not_added" || legacyStatus === "contacted" ? "not_added" : "unprocessed",
        unaddedReason: String(item.unaddedReason || ""),
        callCount: Number.isInteger(item.callCount) && Number(item.callCount) >= 0 ? Number(item.callCount) : 0,
      };
    }) as ConsultationRecord[];
    const highestSequence = consultations.reduce((highest, item) => Math.max(highest, item.sequence), 0);
    const teachers = (parsed.teachers as Array<Partial<ConsultationTeacher>>).map((teacher) => ({
      ...teacher,
      role: teacher.role === "operator" ? "operator" : "customer_service",
    })) as ConsultationTeacher[];
    return {
      version: 1,
      teachers,
      consultations,
      lastAssignedTeacherId: typeof parsed.lastAssignedTeacherId === "string" ? parsed.lastAssignedTeacherId : null,
      nextConsultationNumber: Math.max(Number(parsed.nextConsultationNumber) || 1, highestSequence + 1),
    };
  } 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;
}

function normalizePhone(value: unknown): string {
  const phone = String(value || "").replace(/\s+/g, "");
  if (!/^1[3-9]\d{9}$/.test(phone)) throw new StoreError("请输入正确的 11 位手机号");
  return phone;
}

function normalizeCallCount(value: unknown): number {
  const count = Number(value ?? 0);
  if (!Number.isInteger(count) || count < 0 || count > 9999) throw new StoreError("打电话次数需为 0-9999 的整数");
  return count;
}

function normalizeConsultationStatus(value: unknown): ConsultationStatus {
  const status = String(value || "unprocessed") as ConsultationStatus;
  if (!["unprocessed", "not_added", "added"].includes(status)) throw new StoreError("用户状态无效");
  return status;
}

function normalizeStaffRole(value: unknown): StaffRole {
  return value === "operator" ? "operator" : "customer_service";
}

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(),
      role: normalizeStaffRole(input.role),
      name: cleanText(input.name, "姓名", 30),
      username,
      title: cleanText(input.title, "展示称谓", 40, false),
      wechat: cleanText(input.wechat, "微信号", 60, false),
      qrCodeUrl: "",
      sort: normalizeSort(input.sort || 1),
      enabled: input.enabled !== false,
      assignedCount: 0,
      lastAssignedAt: null,
      ...(await passwordFields(input.password)),
      sessionVersion: 1,
      createdAt: now,
      updatedAt: now,
    };
    if (teacher.role === "customer_service") {
      teacher.title = teacher.title || "教务老师｜一对一学情沟通";
      teacher.qrCodeUrl = normalizeQrCodeUrl(input.qrCodeUrl);
    }
    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.role = normalizeStaffRole(input.role ?? teacher.role);
    teacher.name = cleanText(input.name, "姓名", 30);
    teacher.username = username;
    teacher.title = cleanText(input.title, "展示称谓", 40, false);
    teacher.wechat = cleanText(input.wechat, "微信号", 60, false);
    teacher.qrCodeUrl = teacher.role === "customer_service" ? normalizeQrCodeUrl(input.qrCodeUrl) : "";
    teacher.sort = normalizeSort(input.sort || teacher.sort || 1);
    if (teacher.role === "customer_service") teacher.title ||= "教务老师｜一对一学情沟通";
    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 = normalizePhone(input.phone);
    const teachers = state.teachers.filter((teacher) => teacher.enabled && teacher.role === "customer_service").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(),
      sequence: state.nextConsultationNumber++,
      name: "",
      phone,
      location: "",
      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: "unprocessed",
      unaddedReason: "",
      callCount: 0,
      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 createConsultation(input: Record<string, unknown>): Promise<ConsultationRecord> {
  return mutate((state) => {
    const teacher = state.teachers.find((item) => item.id === String(input.teacherId || "") && item.role === "customer_service");
    if (!teacher) throw new StoreError("请选择分配客服");
    const now = new Date().toISOString();
    const consultation: ConsultationRecord = {
      id: crypto.randomUUID(),
      sequence: state.nextConsultationNumber++,
      name: cleanText(input.name, "姓名", 30, false),
      phone: normalizePhone(input.phone),
      location: cleanText(input.location, "归属地", 80, false),
      teacherId: teacher.id,
      teacherName: teacher.name,
      teacherTitle: teacher.title,
      teacherWechat: teacher.wechat,
      teacherQrCodeUrl: teacher.qrCodeUrl,
      source: cleanText(input.source, "来源", 80, false) || "后台录入",
      page: "",
      status: normalizeConsultationStatus(input.status),
      unaddedReason: cleanText(input.unaddedReason, "未添加原因", 200, false),
      callCount: normalizeCallCount(input.callCount),
      note: cleanText(input.note, "跟进备注", 500, false),
      createdAt: now,
      updatedAt: now,
    };
    state.consultations.unshift(consultation);
    return consultation;
  });
}

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);
    consultation.status = normalizeConsultationStatus(input.status || consultation.status);
    consultation.phone = normalizePhone(input.phone ?? consultation.phone);
    consultation.note = cleanText(input.note, "跟进备注", 500, false);
    consultation.name = cleanText(input.name ?? consultation.name, "姓名", 30, false);
    consultation.location = cleanText(input.location ?? consultation.location, "归属地", 80, false);
    consultation.unaddedReason = cleanText(input.unaddedReason ?? consultation.unaddedReason, "未添加原因", 200, false);
    consultation.callCount = normalizeCallCount(input.callCount ?? consultation.callCount);
    if (!teacherId && input.teacherId && String(input.teacherId) !== consultation.teacherId) {
      const teacher = state.teachers.find((item) => item.id === String(input.teacherId) && item.role === "customer_service");
      if (!teacher) throw new StoreError("请选择分配客服");
      consultation.teacherId = teacher.id;
      consultation.teacherName = teacher.name;
      consultation.teacherTitle = teacher.title;
      consultation.teacherWechat = teacher.wechat;
      consultation.teacherQrCodeUrl = teacher.qrCodeUrl;
    }
    consultation.updatedAt = new Date().toISOString();
    return consultation;
  });
}

export async function deleteConsultation(id: string): Promise<void> {
  await mutate((state) => {
    const index = state.consultations.findIndex((item) => item.id === id);
    if (index < 0) throw new StoreError("用户线索不存在", 404);
    state.consultations.splice(index, 1);
  });
}
