import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';

type ImportRow = { email?: string; workCity?: string };

type CategorizedCity =
  | { category: 'exact'; city: string; rowCount: number }
  | { category: 'similar'; city: string; similarTo: string; distance: number; rowCount: number }
  | { category: 'new'; city: string; rowCount: number };

/**
 * v1.2 用户工作地 Excel 导入预览服务
 *
 * 流程：
 * 1) preview：前端上传 rows（[{email, workCity}]）→ 本服务把 rows 里城市按"已有/相似/全新"三类归并
 * 2) commit：前端带 approvals（每个城市的处理方式）+ rows 重新提交 → 本服务按规则写入 User.workCity
 *
 * 相似度算法：Levenshtein 编辑距离
 *   - 仅当 |len(a) - len(b)| <= 2 && distance <= 2 时才算相似（排除缩写如 LA vs Los Angeles）
 */
@Injectable()
export class WorkCityImportService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Levenshtein 编辑距离
   */
  static editDistance(a: string, b: string): number {
    const m = a.length;
    const n = b.length;
    if (m === 0) return n;
    if (n === 0) return m;
    const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
    for (let i = 0; i <= m; i += 1) dp[i][0] = i;
    for (let j = 0; j <= n; j += 1) dp[0][j] = j;
    for (let i = 1; i <= m; i += 1) {
      for (let j = 1; j <= n; j += 1) {
        const cost = a[i - 1] === b[j - 1] ? 0 : 1;
        dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
      }
    }
    return dp[m][n];
  }

  /**
   * 判定两个城市字符串是否相似（拼写错误范围内；排除缩写）
   */
  static isSimilarCity(a: string, b: string): { similar: boolean; distance: number } {
    const distance = WorkCityImportService.editDistance(a.toLowerCase(), b.toLowerCase());
    const lenDiff = Math.abs(a.length - b.length);
    return { similar: lenDiff <= 2 && distance <= 2 && distance > 0, distance };
  }

  /**
   * 聚合已有城市（系统内 users.work_city + meetings.city + meeting_series.city 非空去重）
   */
  async listKnownCities(): Promise<string[]> {
    const [userRows, meetingRows, seriesRows] = await Promise.all([
      this.prisma.user.findMany({
        where: { deletedAt: null, workCity: { not: null } } as any,
        select: { workCity: true } as any,
        distinct: ['workCity' as any],
      }),
      (this.prisma as any).meeting.findMany({
        where: { city: { not: null } },
        select: { city: true },
        distinct: ['city'],
      }),
      (this.prisma as any).meetingSeries.findMany({
        where: { city: { not: null } },
        select: { city: true },
        distinct: ['city'],
      }),
    ]);

    const set = new Set<string>();
    for (const r of userRows as any[]) if (r.workCity?.trim()) set.add(r.workCity.trim());
    for (const r of meetingRows) if (r.city?.trim()) set.add(r.city.trim());
    for (const r of seriesRows) if (r.city?.trim()) set.add(r.city.trim());
    return Array.from(set);
  }

  /**
   * 预览：把 rows 里出现的城市分成已有 / 相似 / 全新
   * 也返回不能匹配 User 的邮箱列表
   */
  async preview(rows: ImportRow[]) {
    const knownCities = await this.listKnownCities();

    const cityCount = new Map<string, number>();
    const trimmedRows: { email: string; workCity: string }[] = [];
    for (const r of rows) {
      const email = (r.email || '').trim().toLowerCase();
      const city = (r.workCity || '').trim();
      if (!email || !city) continue;
      trimmedRows.push({ email, workCity: city });
      cityCount.set(city, (cityCount.get(city) || 0) + 1);
    }

    const exact: CategorizedCity[] = [];
    const similar: CategorizedCity[] = [];
    const newCities: CategorizedCity[] = [];
    for (const [city, rowCount] of cityCount.entries()) {
      if (knownCities.includes(city)) {
        exact.push({ category: 'exact', city, rowCount });
        continue;
      }
      const similarHit = knownCities
        .map((known) => ({ known, ...WorkCityImportService.isSimilarCity(city, known) }))
        .filter((x) => x.similar)
        .sort((a, b) => a.distance - b.distance)[0];
      if (similarHit) {
        similar.push({
          category: 'similar',
          city,
          similarTo: similarHit.known,
          distance: similarHit.distance,
          rowCount,
        });
      } else {
        newCities.push({ category: 'new', city, rowCount });
      }
    }

    const emailSet = Array.from(new Set(trimmedRows.map((r) => r.email)));
    const matchedUsers = emailSet.length
      ? await this.prisma.user.findMany({
          where: { email: { in: emailSet }, deletedAt: null } as any,
          select: { id: true, email: true } as any,
        })
      : [];
    const matchedEmails = new Set(matchedUsers.map((u: any) => u.email.toLowerCase()));
    const unmatchedEmails = emailSet.filter((e) => !matchedEmails.has(e));

    return {
      total: trimmedRows.length,
      uniqueCitiesInFile: cityCount.size,
      categorized: {
        exact,
        similar,
        new: newCities,
      },
      unmatchedEmails,
    };
  }

  /**
   * 提交：按 approvals 决议后，把 workCity 写入 User
   *
   * approvals 是一个 map：{ [city]: { action: 'use' | 'replace' | 'skip', targetCity?: string } }
   * - `use`: 按原文写入（全新 / 已有精确匹配）
   * - `replace`: 把原文映射成 `targetCity`（相似警告命中已有城市后统一拼法）
   * - `skip`: 本次不导入此城市的所有行
   *
   * 未列出的城市默认 `use`。
   */
  async commit(
    rows: ImportRow[],
    approvals: Record<string, { action: 'use' | 'replace' | 'skip'; targetCity?: string }>,
    actorUserId?: string,
  ) {
    let created = 0;
    let updated = 0;
    let skipped = 0;
    const unmatchedEmails: string[] = [];

    for (const raw of rows) {
      const email = (raw.email || '').trim().toLowerCase();
      const originalCity = (raw.workCity || '').trim();
      if (!email || !originalCity) {
        skipped += 1;
        continue;
      }
      const decision = approvals[originalCity];
      if (decision?.action === 'skip') {
        skipped += 1;
        continue;
      }
      const targetCity =
        decision?.action === 'replace' && decision.targetCity ? decision.targetCity.trim() : originalCity;

      const user = await this.prisma.user.findFirst({
        where: { email, deletedAt: null } as any,
        select: { id: true, workCity: true } as any,
      });
      if (!user) {
        unmatchedEmails.push(email);
        skipped += 1;
        continue;
      }

      const hadValue = !!(user as any).workCity;
      await (this.prisma.user as any).update({
        where: { id: user.id },
        data: { workCity: targetCity },
      });
      if (hadValue) updated += 1;
      else created += 1;
    }

    return { created, updated, skipped, unmatchedEmails };
  }
}
