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;
  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">;
export type ConsultationListItem = ConsultationRecord & {
  teacherName: string;
  teacherTitle: string;
  teacherWechat: string;
  teacherQrCodeUrl: string;
};

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 || "");
      const storedLocation = String(item.location || "");
      return {
        id: String(item.id || ""),
        sequence: Number.isInteger(item.sequence) ? Number(item.sequence) : total - index,
        name: String(item.name || ""),
        phone: String(item.phone || ""),
        location: storedLocation,
        teacherId: String(item.teacherId || ""),
        source: String(item.source || ""),
        page: String(item.page || ""),
        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,
        note: String(item.note || ""),
        createdAt: String(item.createdAt || ""),
        updatedAt: String(item.updatedAt || item.createdAt || ""),
      };
    }) 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 cleanMultilineText(value: unknown, label: string, maxLength: number): string {
  const text = String(value || "")
    .replace(/\r\n?/g, "\n")
    .replace(/[ \t]+$/gm, "")
    .trim();
  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 inferCarrierLocation(value: unknown): string {
  const phone = String(value || "").replace(/\s+/g, "");
  if (!/^1[3-9]\d{9}$/.test(phone)) return "";
  const prefix = phone.slice(0, 3);
  const carrier = /^(134|135|136|137|138|139|147|148|150|151|152|157|158|159|172|178|182|183|184|187|188|195|197|198)$/.test(prefix)
    ? "中国移动"
    : /^(130|131|132|145|155|156|166|175|176|185|186|196)$/.test(prefix)
      ? "中国联通"
      : /^(133|149|153|173|174|177|180|181|189|190|191|193|199)$/.test(prefix)
        ? "中国电信"
        : /^(162|165|167|170|171)$/.test(prefix) ? "虚拟运营商" : "运营商未知";
  return `中国大陆 ${carrier}`;
}

function provinceName(value: unknown): string {
  const name = String(value || "").trim();
  if (!name) return "";
  if (/(省|市|自治区|特别行政区)$/.test(name)) return name;
  const autonomousRegions: Record<string, string> = { 内蒙古: "内蒙古自治区", 广西: "广西壮族自治区", 西藏: "西藏自治区", 宁夏: "宁夏回族自治区", 新疆: "新疆维吾尔自治区" };
  if (autonomousRegions[name]) return autonomousRegions[name];
  if (["北京", "上海", "天津", "重庆"].includes(name)) return `${name}市`;
  return `${name}省`;
}

function cityName(value: unknown): string {
  const name = String(value || "").trim();
  if (!name || /(市|地区|自治州|盟)$/.test(name)) return name;
  return `${name}市`;
}

function carrierName(value: unknown): string {
  const name = String(value || "").trim();
  if (name.includes("移动")) return "中国移动";
  if (name.includes("联通")) return "中国联通";
  if (name.includes("电信")) return "中国电信";
  if (name.includes("广电")) return "中国广电";
  return name;
}

async function resolvePhoneLocation(phone: string): Promise<string> {
  try {
    const response = await fetch(`https://cx.shouji.360.cn/phonearea.php?number=${encodeURIComponent(phone)}`, {
      headers: { Accept: "application/json" },
      signal: AbortSignal.timeout(2500),
    });
    if (response.ok) {
      const result = await response.json() as { code?: number; data?: { province?: string; city?: string; sp?: string } };
      const province = provinceName(result.data?.province);
      const city = cityName(result.data?.city);
      const carrier = carrierName(result.data?.sp);
      if (result.code === 0 && province && city && carrier) return `${province} ${city} ${carrier}`;
    }
  } catch {}
  return inferCarrierLocation(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 consultationListItem(consultation: ConsultationRecord, teacher?: ConsultationTeacher): ConsultationListItem {
  return {
    ...consultation,
    teacherName: teacher?.name || (consultation.teacherId ? "已删除客服" : ""),
    teacherTitle: teacher?.title || "",
    teacherWechat: teacher?.wechat || "",
    teacherQrCodeUrl: 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);
    const nextRole = normalizeStaffRole(input.role ?? teacher.role);
    if (teacher.role === "customer_service" && nextRole !== teacher.role && state.consultations.some((item) => item.teacherId === id)) {
      throw new StoreError("该客服已有分配记录，请先重新分配用户后再修改角色", 409);
    }
    teacher.role = nextRole;
    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);
    if (state.consultations.some((item) => item.teacherId === id)) {
      throw new StoreError("该客服已有分配记录，请停用账号或先重新分配用户", 409);
    }
    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 }> {
  const phone = normalizePhone(input.phone);
  const location = await resolvePhoneLocation(phone);
  return mutate((state) => {
    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,
      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<ConsultationListItem> {
  const phone = normalizePhone(input.phone);
  const location = await resolvePhoneLocation(phone);
  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,
      location,
      teacherId: teacher.id,
      source: cleanText(input.source, "来源", 80, false) || "后台录入",
      page: "",
      status: normalizeConsultationStatus(input.status),
      unaddedReason: cleanMultilineText(input.unaddedReason, "未添加原因", 200),
      callCount: normalizeCallCount(input.callCount),
      note: cleanMultilineText(input.note, "跟进备注", 500),
      createdAt: now,
      updatedAt: now,
    };
    state.consultations.unshift(consultation);
    return consultationListItem(consultation, teacher);
  });
}

export async function listConsultations(teacherId?: string): Promise<ConsultationListItem[]> {
  const state = await readState();
  const teachers = new Map(state.teachers.map((teacher) => [teacher.id, teacher]));
  return state.consultations
    .filter((item) => !teacherId || item.teacherId === teacherId)
    .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
    .map((item) => consultationListItem(item, teachers.get(item.teacherId)));
}

export async function updateConsultation(id: string, input: Record<string, unknown>, teacherId?: string): Promise<ConsultationListItem> {
  return mutate(async (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);
    const nextPhone = normalizePhone(input.phone ?? consultation.phone);
    const phoneChanged = nextPhone !== consultation.phone;
    consultation.phone = nextPhone;
    consultation.note = cleanMultilineText(input.note, "跟进备注", 500);
    consultation.name = cleanText(input.name ?? consultation.name, "姓名", 30, false);
    if (phoneChanged || !consultation.location) consultation.location = await resolvePhoneLocation(consultation.phone);
    consultation.unaddedReason = cleanMultilineText(input.unaddedReason ?? consultation.unaddedReason, "未添加原因", 200);
    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.updatedAt = new Date().toISOString();
    return consultationListItem(consultation, state.teachers.find((item) => item.id === consultation.teacherId));
  });
}

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);
  });
}
