import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Place } from 'src/database';
import { HistoryBuilder, Property } from 'src/decorators';
import { AlbumResponseDto } from 'src/dtos/album.dto';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AssetOrder, AssetType, AssetVisibility } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation';

class BaseSearchDto {
  @ValidateUUID({ optional: true, nullable: true })
  libraryId?: string | null;

  @IsString()
  @IsNotEmpty()
  @Optional()
  deviceId?: string;

  @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', optional: true })
  type?: AssetType;

  @ValidateBoolean({ optional: true })
  isEncoded?: boolean;

  @ValidateBoolean({ optional: true })
  isFavorite?: boolean;

  @ValidateBoolean({ optional: true })
  isMotion?: boolean;

  @ValidateBoolean({ optional: true })
  isOffline?: boolean;

  @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
  visibility?: AssetVisibility;

  @ValidateDate({ optional: true })
  createdBefore?: Date;

  @ValidateDate({ optional: true })
  createdAfter?: Date;

  @ValidateDate({ optional: true })
  updatedBefore?: Date;

  @ValidateDate({ optional: true })
  updatedAfter?: Date;

  @ValidateDate({ optional: true })
  trashedBefore?: Date;

  @ValidateDate({ optional: true })
  trashedAfter?: Date;

  @ValidateDate({ optional: true })
  takenBefore?: Date;

  @ValidateDate({ optional: true })
  takenAfter?: Date;

  @IsString()
  @Optional({ nullable: true, emptyToNull: true })
  city?: string | null;

  @IsString()
  @Optional({ nullable: true, emptyToNull: true })
  state?: string | null;

  @IsString()
  @IsNotEmpty()
  @Optional({ nullable: true, emptyToNull: true })
  country?: string | null;

  @IsString()
  @Optional({ nullable: true, emptyToNull: true })
  make?: string;

  @IsString()
  @Optional({ nullable: true, emptyToNull: true })
  model?: string | null;

  @IsString()
  @Optional({ nullable: true, emptyToNull: true })
  lensModel?: string | null;

  @ValidateBoolean({ optional: true })
  isNotInAlbum?: boolean;

  @ValidateUUID({ each: true, optional: true })
  personIds?: string[];

  @ValidateUUID({ each: true, optional: true, nullable: true })
  tagIds?: string[] | null;

  @ValidateUUID({ each: true, optional: true })
  albumIds?: string[];

  @Optional()
  @IsInt()
  @Max(5)
  @Min(-1)
  rating?: number;

  @IsString()
  @IsNotEmpty()
  @Optional()
  ocr?: string;
}

class BaseSearchWithResultsDto extends BaseSearchDto {
  @ValidateBoolean({ optional: true })
  withDeleted?: boolean;

  @ValidateBoolean({ optional: true })
  withExif?: boolean;

  @IsInt()
  @Min(1)
  @Max(1000)
  @Type(() => Number)
  @Optional()
  size?: number;
}

export class RandomSearchDto extends BaseSearchWithResultsDto {
  @ValidateBoolean({ optional: true })
  withStacked?: boolean;

  @ValidateBoolean({ optional: true })
  withPeople?: boolean;
}

export class LargeAssetSearchDto extends BaseSearchWithResultsDto {
  @Optional()
  @IsInt()
  @Min(0)
  @Type(() => Number)
  @ApiProperty({ type: 'integer' })
  minFileSize?: number;
}

export class MetadataSearchDto extends RandomSearchDto {
  @ValidateUUID({ optional: true })
  id?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  deviceAssetId?: string;

  @ValidateString({ optional: true, trim: true })
  description?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  checksum?: string;

  @ValidateString({ optional: true, trim: true })
  originalFileName?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  originalPath?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  previewPath?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  thumbnailPath?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  encodedVideoPath?: string;

  @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.Desc })
  order?: AssetOrder;

  @IsInt()
  @Min(1)
  @Type(() => Number)
  @Optional()
  page?: number;
}

export class StatisticsSearchDto extends BaseSearchDto {
  @ValidateString({ optional: true, trim: true })
  description?: string;
}

export class SmartSearchDto extends BaseSearchWithResultsDto {
  @ValidateString({ optional: true, trim: true })
  query?: string;

  @ValidateUUID({ optional: true })
  @Optional()
  queryAssetId?: string;

  @IsString()
  @IsNotEmpty()
  @Optional()
  language?: string;

  @IsInt()
  @Min(1)
  @Type(() => Number)
  @Optional()
  page?: number;
}

export class SearchPlacesDto {
  @IsString()
  @IsNotEmpty()
  name!: string;
}

export class SearchPeopleDto {
  @IsString()
  @IsNotEmpty()
  name!: string;

  @ValidateBoolean({ optional: true })
  withHidden?: boolean;
}

export class PlacesResponseDto {
  name!: string;
  latitude!: number;
  longitude!: number;
  admin1name?: string;
  admin2name?: string;
}

export function mapPlaces(place: Place): PlacesResponseDto {
  return {
    name: place.name,
    latitude: place.latitude,
    longitude: place.longitude,
    admin1name: place.admin1Name ?? undefined,
    admin2name: place.admin2Name ?? undefined,
  };
}

export enum SearchSuggestionType {
  COUNTRY = 'country',
  STATE = 'state',
  CITY = 'city',
  CAMERA_MAKE = 'camera-make',
  CAMERA_MODEL = 'camera-model',
  CAMERA_LENS_MODEL = 'camera-lens-model',
}

export class SearchSuggestionRequestDto {
  @ValidateEnum({ enum: SearchSuggestionType, name: 'SearchSuggestionType' })
  type!: SearchSuggestionType;

  @IsString()
  @Optional()
  country?: string;

  @IsString()
  @Optional()
  state?: string;

  @IsString()
  @Optional()
  make?: string;

  @IsString()
  @Optional()
  model?: string;

  @IsString()
  @Optional()
  lensModel?: string;

  @ValidateBoolean({ optional: true })
  @Property({ history: new HistoryBuilder().added('v1.111.0').stable('v2') })
  includeNull?: boolean;
}

class SearchFacetCountResponseDto {
  @ApiProperty({ type: 'integer' })
  count!: number;
  value!: string;
}

class SearchFacetResponseDto {
  fieldName!: string;
  counts!: SearchFacetCountResponseDto[];
}

class SearchAlbumResponseDto {
  @ApiProperty({ type: 'integer' })
  total!: number;
  @ApiProperty({ type: 'integer' })
  count!: number;
  items!: AlbumResponseDto[];
  facets!: SearchFacetResponseDto[];
}

class SearchAssetResponseDto {
  @ApiProperty({ type: 'integer' })
  total!: number;
  @ApiProperty({ type: 'integer' })
  count!: number;
  items!: AssetResponseDto[];
  facets!: SearchFacetResponseDto[];
  nextPage!: string | null;
}

export class SearchResponseDto {
  albums!: SearchAlbumResponseDto;
  assets!: SearchAssetResponseDto;
}

export class SearchStatisticsResponseDto {
  @ApiProperty({ type: 'integer' })
  total!: number;
}

class SearchExploreItem {
  value!: string;
  data!: AssetResponseDto;
}

export class SearchExploreResponseDto {
  fieldName!: string;
  items!: SearchExploreItem[];
}

export class MemoryLaneDto {
  @IsInt()
  @Type(() => Number)
  @Max(31)
  @Min(1)
  @ApiProperty({ type: 'integer' })
  day!: number;

  @IsInt()
  @Type(() => Number)
  @Max(12)
  @Min(1)
  @ApiProperty({ type: 'integer' })
  month!: number;
}
