import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { CheckpointRepository } from '../repositories/checkpoint.repository';
import { CreateCheckpointDto, UpdateCheckpointDto, AccessMode } from '../dto/checkpoint.dto';
import {
  SiteAttendanceErrorCodes,
  COMPANY_ID_PATTERN,
  MIN_QR_ROTATION_SECONDS,
} from '../error-codes';

@Injectable()
export class CheckpointService {
  constructor(private readonly checkpointRepo: CheckpointRepository) {}

  async findAll() {
    return this.checkpointRepo.findAll();
  }

  async findById(id: string) {
    const checkpoint = await this.checkpointRepo.findById(id);
    if (!checkpoint) {
      throw new NotFoundException({
        code: SiteAttendanceErrorCodes.CHECKPOINT_NOT_FOUND,
        message: 'Checkpoint not found',
      });
    }
    return checkpoint;
  }

  async findActiveByCode(code: string) {
    const checkpoint = await this.checkpointRepo.findActiveByCode(code);
    if (!checkpoint) {
      throw new NotFoundException({
        code: SiteAttendanceErrorCodes.CHECKPOINT_NOT_FOUND,
        message: 'Checkpoint not found or inactive',
      });
    }
    return checkpoint;
  }

  /**
   * v1.5 字段联合校验：
   * - qrRotationSeconds 非 null 时必须 >= MIN_QR_ROTATION_SECONDS
   * - qrGraceSeconds 非负且不大于 rotation
   * - sharedCheckinEnabled=true 时 sharedCompanyId + sharedCompanyLabel 必填
   */
  private validateV15Fields(
    incoming: {
      qrRotationSeconds?: number | null;
      qrGraceSeconds?: number;
      sharedCheckinEnabled?: boolean;
      sharedCompanyId?: string | null;
      sharedCompanyLabel?: string | null;
    },
    effective: {
      qrRotationSeconds?: number | null;
      qrGraceSeconds?: number;
      sharedCheckinEnabled?: boolean;
      sharedCompanyId?: string | null;
      sharedCompanyLabel?: string | null;
    },
  ) {
    if (
      incoming.qrRotationSeconds !== undefined &&
      incoming.qrRotationSeconds !== null &&
      incoming.qrRotationSeconds < MIN_QR_ROTATION_SECONDS
    ) {
      throw new BadRequestException({
        code: SiteAttendanceErrorCodes.CHECKPOINT_QR_ROTATION_INVALID,
        message: `qrRotationSeconds must be null or >= ${MIN_QR_ROTATION_SECONDS}`,
      });
    }
    const grace = effective.qrGraceSeconds ?? 120;
    const rot = effective.qrRotationSeconds;
    if (grace < 0 || (rot != null && grace > rot)) {
      throw new BadRequestException({
        code: SiteAttendanceErrorCodes.CHECKPOINT_QR_GRACE_INVALID,
        message: 'qrGraceSeconds must be between 0 and qrRotationSeconds',
      });
    }

    if (effective.sharedCheckinEnabled) {
      if (!effective.sharedCompanyId || !effective.sharedCompanyLabel) {
        throw new BadRequestException({
          code: SiteAttendanceErrorCodes.CHECKPOINT_SHARED_COMPANY_MISSING,
          message:
            'sharedCompanyId and sharedCompanyLabel are required when sharedCheckinEnabled is true',
        });
      }
      if (!COMPANY_ID_PATTERN.test(effective.sharedCompanyId)) {
        throw new BadRequestException({
          code: SiteAttendanceErrorCodes.CHECKPOINT_SHARED_COMPANYID_INVALID,
          message: 'sharedCompanyId must match [a-z0-9_-]+',
        });
      }
    }
  }

  async create(dto: CreateCheckpointDto, createdBy: string) {
    const effective = {
      qrRotationSeconds: dto.qrRotationSeconds ?? 3600,
      qrGraceSeconds: dto.qrGraceSeconds ?? 120,
      sharedCheckinEnabled: dto.sharedCheckinEnabled ?? false,
      sharedCompanyId: dto.sharedCompanyId ?? null,
      sharedCompanyLabel: dto.sharedCompanyLabel ?? null,
    };
    this.validateV15Fields(dto, effective);

    return this.checkpointRepo.create({
      name: dto.name,
      description: dto.description,
      address: dto.address,
      timezone: dto.timezone,
      latitude: dto.latitude,
      longitude: dto.longitude,
      geoPolicy: dto.geoPolicy ?? 'SKIP',
      geoRadius: dto.geoRadius ?? 200,
      geoAccuracyThreshold: dto.geoAccuracyThreshold ?? 100,
      allowUnauthenticatedCheckin: dto.allowUnauthenticatedCheckin ?? false,
      accessMode: dto.accessMode ?? AccessMode.SIGNED,
      qrRotationSeconds: effective.qrRotationSeconds,
      qrGraceSeconds: effective.qrGraceSeconds,
      sharedCheckinEnabled: effective.sharedCheckinEnabled,
      sharedCompanyId: effective.sharedCompanyId,
      sharedCompanyLabel: effective.sharedCompanyLabel,
      creator: { connect: { id: createdBy } },
    });
  }

  async update(id: string, dto: UpdateCheckpointDto) {
    const existing = await this.findById(id);

    const effective = {
      qrRotationSeconds:
        dto.qrRotationSeconds !== undefined
          ? dto.qrRotationSeconds
          : existing.qrRotationSeconds,
      qrGraceSeconds:
        dto.qrGraceSeconds !== undefined
          ? dto.qrGraceSeconds
          : existing.qrGraceSeconds,
      sharedCheckinEnabled:
        dto.sharedCheckinEnabled !== undefined
          ? dto.sharedCheckinEnabled
          : existing.sharedCheckinEnabled,
      sharedCompanyId:
        dto.sharedCompanyId !== undefined
          ? dto.sharedCompanyId
          : existing.sharedCompanyId,
      sharedCompanyLabel:
        dto.sharedCompanyLabel !== undefined
          ? dto.sharedCompanyLabel
          : existing.sharedCompanyLabel,
    };
    this.validateV15Fields(dto, effective);

    const data: Record<string, any> = {};
    if (dto.name !== undefined) data.name = dto.name;
    if (dto.description !== undefined) data.description = dto.description;
    if (dto.address !== undefined) data.address = dto.address;
    if (dto.timezone !== undefined) data.timezone = dto.timezone;
    if (dto.latitude !== undefined) data.latitude = dto.latitude;
    if (dto.longitude !== undefined) data.longitude = dto.longitude;
    if (dto.geoPolicy !== undefined) data.geoPolicy = dto.geoPolicy;
    if (dto.geoRadius !== undefined) {
      if (dto.geoRadius < 100) {
        throw new BadRequestException('geoRadius must be at least 100 meters');
      }
      data.geoRadius = dto.geoRadius;
    }
    if (dto.geoAccuracyThreshold !== undefined) data.geoAccuracyThreshold = dto.geoAccuracyThreshold;
    if (dto.allowUnauthenticatedCheckin !== undefined) data.allowUnauthenticatedCheckin = dto.allowUnauthenticatedCheckin;
    if (dto.isActive !== undefined) data.isActive = dto.isActive;
    if (dto.accessMode !== undefined) data.accessMode = dto.accessMode;
    if (dto.qrRotationSeconds !== undefined) data.qrRotationSeconds = dto.qrRotationSeconds;
    if (dto.qrGraceSeconds !== undefined) data.qrGraceSeconds = dto.qrGraceSeconds;
    if (dto.sharedCheckinEnabled !== undefined) data.sharedCheckinEnabled = dto.sharedCheckinEnabled;
    if (dto.sharedCompanyId !== undefined) data.sharedCompanyId = dto.sharedCompanyId;
    if (dto.sharedCompanyLabel !== undefined) data.sharedCompanyLabel = dto.sharedCompanyLabel;

    return this.checkpointRepo.update(id, data);
  }

  async remove(id: string) {
    await this.findById(id);
    return this.checkpointRepo.delete(id);
  }
}
