import { GpsFixType, LocateMode, UtilityType } from '@prisma/client';
import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
  ArrayMaxSize,
  ArrayMinSize,
  IsArray,
  IsDateString,
  IsEnum,
  IsIn,
  IsInt,
  IsNotEmpty,
  IsNumber,
  IsOptional,
  IsString,
  Max,
  MaxLength,
  Min,
  ValidateNested,
} from 'class-validator';

// Field names here are terse (lat/lng/alt/hAcc/...) to keep MQTT payloads
// small; this same shape is reused as the sim tool's REST request body.
export class MqttPointDto {
  @ApiProperty({ minimum: -90, maximum: 90 })
  @IsNumber()
  @Min(-90)
  @Max(90)
  lat: number;

  @ApiProperty({ minimum: -180, maximum: 180 })
  @IsNumber()
  @Min(-180)
  @Max(180)
  lng: number;

  @ApiPropertyOptional({ description: 'Altitude, meters' })
  @IsOptional()
  @IsNumber()
  alt?: number;

  @ApiPropertyOptional({ enum: GpsFixType })
  @IsOptional()
  @IsEnum(GpsFixType)
  fix?: GpsFixType;

  @ApiPropertyOptional({ description: 'Horizontal accuracy, meters', minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  hAcc?: number;

  @ApiPropertyOptional({ description: 'Meters below grade', minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  depth?: number;

  @ApiPropertyOptional({ enum: UtilityType })
  @IsOptional()
  @IsEnum(UtilityType)
  utility?: UtilityType;

  @ApiPropertyOptional({ description: 'Ordering within a locate run' })
  @IsOptional()
  @IsInt()
  seq?: number;

  // GPS quality
  @ApiPropertyOptional({ description: 'Vertical accuracy, meters', minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  vAcc?: number;

  @ApiPropertyOptional({ minimum: 0 })
  @IsOptional()
  @IsInt()
  @Min(0)
  sats?: number;

  @ApiPropertyOptional({ minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  hdop?: number;

  // Locator receiver telemetry
  @ApiPropertyOptional({ description: 'Locate frequency, Hz', minimum: 0 })
  @IsOptional()
  @IsInt()
  @Min(0)
  freqHz?: number;

  @ApiPropertyOptional({ description: 'Signal current on the line, mA', minimum: 0 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  currentMa?: number;

  @ApiPropertyOptional({ description: 'Signal strength, dB' })
  @IsOptional()
  @IsNumber()
  signalDb?: number;

  @ApiPropertyOptional({ description: 'Receiver gain, dB' })
  @IsOptional()
  @IsNumber()
  gainDb?: number;

  @ApiPropertyOptional({ enum: LocateMode })
  @IsOptional()
  @IsEnum(LocateMode)
  mode?: LocateMode;

  @ApiPropertyOptional({ description: 'Degrees' })
  @IsOptional()
  @IsNumber()
  phaseDeg?: number;

  @ApiPropertyOptional({ description: 'Line direction, 0-360', minimum: 0, maximum: 360 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Max(360)
  compassDeg?: number;

  @ApiPropertyOptional({ minimum: 0, maximum: 100 })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Max(100)
  distortionPct?: number;

  @ApiProperty({ format: 'date-time', description: 'Device GPS timestamp' })
  @IsDateString()
  ts: string;
}

export class MqttPointsMessageDto {
  // Job is resolved by jobId when present, else by (device org, ticket)
  @IsOptional()
  @IsString()
  jobId?: string;

  @IsOptional()
  @IsString()
  @MaxLength(64)
  ticket?: string;

  // Locator receiver serial number; the publisher (MQTT credential) may be a
  // phone/gateway relaying for one or more locators. Auto-registered on first sight.
  @IsOptional()
  @IsString()
  @MaxLength(64)
  serial?: string;

  @IsArray()
  @ArrayMinSize(1)
  @ArrayMaxSize(500)
  @ValidateNested({ each: true })
  @Type(() => MqttPointDto)
  points: MqttPointDto[];
}

export const MQTT_LOG_MESSAGE_TYPES = ['log', 'status'] as const;
export type MqttLogMessageType = (typeof MQTT_LOG_MESSAGE_TYPES)[number];

// Single reading for a locator that publishes directly (or is relayed) to
// devices/<serial>/log — the serial comes from the topic itself, so identity
// is job-anchored rather than publisher-credential-anchored. `type` decides
// what happens to it: "log" persists a LocatePoint; "status" is an ephemeral
// current-position update, broadcast live but never written to the DB.
export class MqttLogMessageDto extends MqttPointDto {
  @ApiProperty({ enum: MQTT_LOG_MESSAGE_TYPES, description: '"log" persists a point; "status" is live-only' })
  @IsIn(MQTT_LOG_MESSAGE_TYPES)
  type: MqttLogMessageType;

  @ApiProperty({ description: 'Job this reading belongs to; also supplies the organization' })
  @IsString()
  @IsNotEmpty()
  jobId: string;
}

export class MqttJobMessageDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(64)
  ticket: string;

  @IsOptional()
  @IsString()
  @MaxLength(200)
  title?: string;

  @IsOptional()
  @IsString()
  @MaxLength(4000)
  description?: string;

  @IsOptional()
  @IsString()
  @MaxLength(400)
  address?: string;
}
