import { EntityManager, In } from 'typeorm';
import { Organization, Student, StudentDingMapping } from '../entities';

export interface DingTalkStudentInput {
  dingUserId: string;
  name: string;
  mobile?: string;
}

export interface DingTalkStudentConflict {
  dingUserId: string;
  name: string;
  reason: string;
}

export interface DingTalkStudentSyncResult {
  created: number;
  updated: number;
  matched: number;
  skipped: number;
  studentIds: Map<string, number>;
  conflicts: DingTalkStudentConflict[];
}

export async function syncDingTalkStudents(
  manager: EntityManager,
  inputs: DingTalkStudentInput[],
  options: { createMissing?: boolean; updateProfile?: boolean } = {},
): Promise<DingTalkStudentSyncResult> {
  const createMissing = options.createMissing !== false;
  const updateProfile = options.updateProfile !== false;
  const users = new Map<string, DingTalkStudentInput>();
  const conflicts: DingTalkStudentConflict[] = [];

  for (const input of inputs) {
    const dingUserId = input.dingUserId?.trim();
    const name = input.name?.trim();
    const mobile = input.mobile?.trim() || undefined;
    if (!dingUserId || dingUserId.length > 100 || !name || name.length > 50) {
      conflicts.push({ dingUserId: dingUserId || '', name: name || '', reason: '钉钉用户ID或姓名无效' });
      continue;
    }
    if (mobile && mobile.length > 20) {
      conflicts.push({ dingUserId, name, reason: '手机号超过20个字符' });
      continue;
    }
    if (!users.has(dingUserId)) users.set(dingUserId, { dingUserId, name, mobile });
  }

  if (users.size === 0) {
    return { created: 0, updated: 0, matched: 0, skipped: 0, studentIds: new Map(), conflicts };
  }

  const dingUserIds = [...users.keys()];
  const mappings = await manager.find(StudentDingMapping, {
    where: { dingUserId: In(dingUserIds) },
  });
  const mappingByDingId = new Map(mappings.map((mapping) => [mapping.dingUserId, mapping]));
  const mappedStudentIds = mappings.map((mapping) => mapping.studentId);
  const mappedStudents = mappedStudentIds.length
    ? await manager.find(Student, { where: { id: In(mappedStudentIds) } })
    : [];
  const studentById = new Map(mappedStudents.map((student) => [student.id, student]));
  const studentIds = new Map<string, number>();
  const updates: Student[] = [];
  let updatedCount = 0;

  for (const mapping of mappings) {
    const input = users.get(mapping.dingUserId);
    const student = studentById.get(mapping.studentId);
    if (!input || !student) {
      conflicts.push({
        dingUserId: mapping.dingUserId,
        name: input?.name || '',
        reason: '钉钉映射对应的学生不存在',
      });
      continue;
    }
    studentIds.set(mapping.dingUserId, student.id);
    if (!updateProfile) continue;
    let changed = false;
    if (student.name !== input.name) {
      student.name = input.name;
      changed = true;
    }
    if (input.mobile && student.phone !== input.mobile) {
      student.phone = input.mobile;
      changed = true;
    }
    if (changed) {
      updates.push(student);
      updatedCount++;
    }
  }

  const newUsers = [...users.values()].filter((user) => !mappingByDingId.has(user.dingUserId));
  const mobiles = [...new Set(newUsers.map((user) => user.mobile).filter((mobile): mobile is string => !!mobile))];
  const occupiedPhones = mobiles.length
    ? await manager.find(Student, { where: { phone: In(mobiles) } })
    : [];
  const studentsByPhone = new Map<string, Student[]>();
  for (const student of occupiedPhones) {
    const list = studentsByPhone.get(student.phone) ?? [];
    list.push(student);
    studentsByPhone.set(student.phone, list);
  }

  // 新钉钉用户按手机号匹配：唯一命中 → 自动绑定；多人同号 → 冲突；无命中 → 新建
  const boundStudentIds = new Set(mappings.map((mapping) => mapping.studentId));
  const bindable: DingTalkStudentInput[] = [];
  const creatable: DingTalkStudentInput[] = [];
  const skipped: DingTalkStudentInput[] = [];
  for (const user of newUsers) {
    if (!user.mobile) {
      (createMissing ? creatable : skipped).push(user);
      continue;
    }
    const matches = studentsByPhone.get(user.mobile) ?? [];
    if (matches.length === 0) {
      (createMissing ? creatable : skipped).push(user);
      continue;
    }
    if (matches.length > 1) {
      conflicts.push({
        dingUserId: user.dingUserId,
        name: user.name,
        reason: '手机号匹配到多名学生，请人工绑定',
      });
      continue;
    }
    const student = matches[0];
    if (boundStudentIds.has(student.id)) {
      conflicts.push({
        dingUserId: user.dingUserId,
        name: user.name,
        reason: '手机号对应的学生已绑定其他钉钉账号',
      });
      continue;
    }
    boundStudentIds.add(student.id);
    bindable.push(user);
  }

  const host = creatable.length
    ? await manager.findOne(Organization, { where: { isHost: true, status: 'active' } })
    : null;
  if (creatable.length && !host) throw new Error('尚未配置本机构');

  const bindableStudent = new Map<string, Student>();
  for (const user of bindable) {
    const student = studentsByPhone.get(user.mobile!)![0];
    bindableStudent.set(user.dingUserId, student);
    if (!updateProfile) continue;
    let changed = false;
    if (student.name !== user.name) {
      student.name = user.name;
      changed = true;
    }
    if (user.mobile && student.phone !== user.mobile) {
      student.phone = user.mobile;
      changed = true;
    }
    if (changed) {
      updates.push(student);
      updatedCount++;
    }
  }

  if (updates.length) await manager.save(Student, updates);

  if (bindable.length) {
    await manager.save(
      StudentDingMapping,
      bindable.map((user) =>
        manager.create(StudentDingMapping, {
          dingUserId: user.dingUserId,
          studentId: bindableStudent.get(user.dingUserId)!.id,
        }),
      ),
    );
    for (const user of bindable) {
      studentIds.set(user.dingUserId, bindableStudent.get(user.dingUserId)!.id);
    }
  }

  const createdStudents = creatable.length
    ? await manager.save(
        Student,
        creatable.map((user) =>
          manager.create(Student, {
            name: user.name,
            phone: user.mobile,
            status: 'active',
            organizationId: host!.id,
          }),
        ),
      )
    : [];
  if (createdStudents.length) {
    await manager.save(
      StudentDingMapping,
      createdStudents.map((student, index) =>
        manager.create(StudentDingMapping, {
          dingUserId: creatable[index].dingUserId,
          studentId: student.id,
        }),
      ),
    );
    createdStudents.forEach((student, index) => studentIds.set(creatable[index].dingUserId, student.id));
  }

  return {
    created: createdStudents.length,
    updated: updatedCount,
    matched: bindable.length,
    skipped: skipped.length,
    studentIds,
    conflicts,
  };
}
