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

export const APP_FIX_TYPES = [
  'AUTONOMOUS',
  'DGPS',
  'FLOAT',
  'FIXED',
  'NO_FIX',
] as const;
export type AppFixType = (typeof APP_FIX_TYPES)[number];

// Frozen app MQTT wire profile v1 from meta/contracts/telemetry-schema.md. This is deliberately
// separate from MqttPointDto: the app wire uses the normative fix tokens FLOAT/FIXED/NO_FIX,
// while the legacy device path uses Prisma's FLOAT_RTK/FIXED_RTK/NONE storage tokens.
export class AppLogPointDto {
  @ApiProperty({
    format: 'uuid',
    description: 'Client-generated UUIDv7 idempotency key.',
  })
  @IsUUID()
  pointId: string;

  @ApiProperty({ format: 'date-time' })
  @IsDateString()
  createdAt: string;

  @IsIn(['APP'])
  origin: 'APP';

  @IsIn(['APP_MQTT'])
  uploadPath: 'APP_MQTT';

  @IsNumber()
  @Min(-90)
  @Max(90)
  lat: number;

  @IsNumber()
  @Min(-180)
  @Max(180)
  lng: number;

  @IsOptional()
  @IsNumber()
  alt?: number;

  @IsDateString()
  ts: string;

  @IsOptional()
  @IsIn(APP_FIX_TYPES)
  fix?: AppFixType;

  @IsOptional()
  @IsNumber()
  @Min(0)
  hAcc?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  vAcc?: number;

  @IsOptional()
  @IsInt()
  @Min(0)
  sats?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  hdop?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  depth?: number;

  @IsOptional()
  @IsInt()
  @Min(0)
  freqHz?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  currentMa?: number;

  @IsOptional()
  @IsNumber()
  signalDb?: number;

  @IsOptional()
  @IsNumber()
  gainDb?: number;

  @IsOptional()
  @IsEnum(LocateMode)
  mode?: LocateMode;

  @IsOptional()
  @IsNumber()
  phaseDeg?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  @Max(360)
  compassDeg?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  @Max(100)
  distortionPct?: number;

  @IsOptional()
  @IsEnum(UtilityType)
  utility?: UtilityType;

  @IsOptional()
  @IsInt()
  seq?: number;

  @IsIn(['IN_SPEC', 'OUT_OF_SPEC', 'MANUAL', 'PPK_CORRECTED', 'WAIVED', 'NONCOMPLIANT'])
  qualityFlag: 'IN_SPEC' | 'OUT_OF_SPEC' | 'MANUAL' | 'PPK_CORRECTED' | 'WAIVED' | 'NONCOMPLIANT';
}

export class AppLogPointsMessageDto {
  @ApiPropertyOptional({ description: 'MQTT app-log wire profile version.' })
  @IsString()
  @MaxLength(16)
  schemaVersion: string;

  @ApiPropertyOptional({
    description: 'Job id; exactly one of jobId or ticket is required.',
  })
  @IsOptional()
  @IsString()
  jobId?: string;

  @ApiPropertyOptional({
    description: 'Ticket number; exactly one of jobId or ticket is required.',
  })
  @IsOptional()
  @IsString()
  @MaxLength(64)
  ticket?: string;

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

export type AppLogAckOutcome = 'ACCEPTED' | 'DUPLICATE' | 'REJECTED';

export interface AppLogAckResult {
  pointId: string;
  outcome: AppLogAckOutcome;
  reasonCode?: string;
}

export interface AppLogAck {
  schemaVersion: string;
  results: AppLogAckResult[];
}
