import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../modules/prisma/prisma.service';
import { AuthUser } from './current-user.decorator';

const inferredCoordinateRegions = [
  {
    // 第二个平台大量终端坐标位于四川雅安汉源一带，外部平台未提供行政区时按坐标归属到这里。
    code: '511823',
    name: '汉源县',
    level: 3,
    longitude: 102.650417,
    latitude: 29.344275,
    bounds: { minLng: 97.0, maxLng: 108.6, minLat: 26.0, maxLat: 34.6 },
    parents: [
      { code: '510000', name: '四川省', level: 1, longitude: 104.075809, latitude: 30.651239 },
      { code: '511800', name: '雅安市', level: 2, longitude: 103.042375, latitude: 30.010602 },
    ],
  },
] as const;

@Injectable()
export class RegionScopeService {
  constructor(private readonly prisma: PrismaService) {}

  async expandRegionIds(regionIds: string[]) {
    if (!regionIds.length) {
      return [];
    }

    const result = new Set(regionIds);
    let frontier = regionIds;

    while (frontier.length) {
      const children = await this.prisma.region.findMany({
        where: { parentId: { in: frontier } },
        select: { id: true },
      });
      frontier = children.map((child) => child.id).filter((id) => !result.has(id));
      frontier.forEach((id) => result.add(id));
    }

    return [...result];
  }

  async scopedRegionIds(user: AuthUser, requestedRegionId?: string) {
    const allowedRegionIds = await this.expandRegionIds(user.regionIds ?? []);
    if (!requestedRegionId) {
      return allowedRegionIds;
    }

    const requestedRegionIds = await this.expandRegionIds([requestedRegionId]);
    if (!allowedRegionIds.length) {
      return requestedRegionIds;
    }

    return requestedRegionIds.filter((regionId) => allowedRegionIds.includes(regionId));
  }

  async findNearestRegionId(longitude: number, latitude: number, maxDistanceSquared = 1) {
    const regions = await this.prisma.region.findMany({
      where: { longitude: { not: null }, latitude: { not: null }, level: 3 },
      select: { id: true, longitude: true, latitude: true },
    });
    if (!regions.length) {
      return undefined;
    }

    const nearest = regions
      .map((region) => {
        const lng = Number(region.longitude);
        const lat = Number(region.latitude);
        return {
          id: region.id,
          distance: Math.abs(lng - longitude) ** 2 + Math.abs(lat - latitude) ** 2,
        };
      })
      .sort((a, b) => a.distance - b.distance)[0];

    if (nearest && nearest.distance <= maxDistanceSquared) {
      return nearest.id;
    }

    return this.inferRegionByCoordinate(longitude, latitude);
  }

  private async inferRegionByCoordinate(longitude: number, latitude: number) {
    const inferred = inferredCoordinateRegions.find(
      (region) =>
        longitude >= region.bounds.minLng &&
        longitude <= region.bounds.maxLng &&
        latitude >= region.bounds.minLat &&
        latitude <= region.bounds.maxLat,
    );
    if (!inferred) {
      return undefined;
    }

    const china = await this.prisma.region.upsert({
      where: { code: '100000' },
      update: {},
      create: { code: '100000', name: '全国', level: 0, longitude: 104.195397, latitude: 35.86166 },
    });

    let parentId = china.id;
    for (const parent of inferred.parents) {
      const region = await this.prisma.region.upsert({
        where: { code: parent.code },
        update: {
          name: parent.name,
          level: parent.level,
          parentId,
          longitude: parent.longitude,
          latitude: parent.latitude,
        },
        create: {
          code: parent.code,
          name: parent.name,
          level: parent.level,
          parentId,
          longitude: parent.longitude,
          latitude: parent.latitude,
        },
      });
      parentId = region.id;
    }

    const region = await this.prisma.region.upsert({
      where: { code: inferred.code },
      update: {
        name: inferred.name,
        level: inferred.level,
        parentId,
        longitude: inferred.longitude,
        latitude: inferred.latitude,
      },
      create: {
        code: inferred.code,
        name: inferred.name,
        level: inferred.level,
        parentId,
        longitude: inferred.longitude,
        latitude: inferred.latitude,
      },
    });

    return region.id;
  }

  async lampWhere(user: AuthUser, extra: Prisma.StreetLampWhereInput = {}, requestedRegionId?: string) {
    const regionIds = await this.scopedRegionIds(user, requestedRegionId);
    return {
      ...extra,
      ...(regionIds.length ? { regionId: { in: regionIds } } : {}),
      ...(user.tenantId ? { tenantId: user.tenantId } : {}),
    };
  }

  async alarmWhere(user: AuthUser, extra: Prisma.AlarmWhereInput = {}, requestedRegionId?: string) {
    const regionIds = await this.scopedRegionIds(user, requestedRegionId);
    return {
      ...extra,
      ...(regionIds.length ? { regionId: { in: regionIds } } : {}),
      ...(user.tenantId ? { tenantId: user.tenantId } : {}),
    };
  }

  /**
   * 外部推送场景：租户登录仅能操作本租户路灯；若配置了区域管辖则路灯须在允许区域内。
   * 平台登录（无 tenantId）不校验。
   */
  async assertLampRowWritableByUser(
    user: AuthUser,
    lamp: { tenantId: string | null; regionId: string },
  ): Promise<void> {
    if (!user.tenantId) {
      return;
    }
    if (lamp.tenantId !== user.tenantId) {
      throw new NotFoundException('路灯设备不存在');
    }
    const allowed = await this.scopedRegionIds(user);
    if (allowed.length && !allowed.includes(lamp.regionId)) {
      throw new NotFoundException('路灯设备不存在');
    }
  }

  /** 租户用户在指定 region 下创建或迁入路灯时的区域权限（平台用户跳过）。 */
  async assertRegionWritableByTenantUser(user: AuthUser, regionId: string): Promise<void> {
    if (!user.tenantId) {
      return;
    }
    const allowed = await this.scopedRegionIds(user);
    if (allowed.length && !allowed.includes(regionId)) {
      throw new BadRequestException('无权在该区域创建或迁移路灯');
    }
  }
}
