import { createZodDto } from 'nestjs-zod';
import { HistoryBuilder } from 'src/decorators';
import { BulkIdsSchema } from 'src/dtos/asset-ids.response.dto';
import { AssetType, AssetVisibilitySchema } from 'src/enum';
import { AssetStats } from 'src/repositories/asset.repository';
import { IsNotSiblingOf, isoDatetimeToDate, latitudeSchema, longitudeSchema, stringToBool } from 'src/validation';
import z from 'zod';

const UpdateAssetBaseSchema = z
  .object({
    isFavorite: z.boolean().optional().describe('Mark as favorite'),
    visibility: AssetVisibilitySchema.optional(),
    dateTimeOriginal: z.string().optional().describe('Original date and time'),
    latitude: latitudeSchema.optional().describe('Latitude coordinate'),
    longitude: longitudeSchema.optional().describe('Longitude coordinate'),
    rating: z
      .int()
      .min(-1)
      .max(5)
      .nullish()
      .refine((v) => v !== 0, {
        error: 'Rating must be -1 (rejected), 1–5 (starred), or null (unrated); 0 is not valid',
      })
      .describe('Rating in range [1-5] (starred), -1 (rejected), or null (unrated)')
      .meta({
        ...new HistoryBuilder()
          .added('v1')
          .stable('v2')
          .updated('v3', 'Using 0 as a rating is no longer valid.')
          .getExtensions(),
      }),
    description: z.string().optional().describe('Asset description'),
  })
  .refine(
    (data) =>
      (data.latitude === undefined && data.longitude === undefined) ||
      (data.latitude !== undefined && data.longitude !== undefined),
    { message: 'Latitude and longitude must be provided together' },
  );

const AssetBulkUpdateBaseSchema = UpdateAssetBaseSchema.extend({
  ids: z.array(z.uuidv4()).describe('Asset IDs to update'),
  duplicateId: z.string().nullish().describe('Duplicate ID'),
  dateTimeRelative: z.int().optional().describe('Relative time offset in minutes'),
  timeZone: z.string().optional().describe('Time zone (IANA timezone)'),
});

const AssetBulkUpdateSchema = AssetBulkUpdateBaseSchema.pipe(
  IsNotSiblingOf(AssetBulkUpdateBaseSchema, 'dateTimeRelative', ['dateTimeOriginal']),
).meta({ id: 'AssetBulkUpdateDto' });

const UpdateAssetSchema = UpdateAssetBaseSchema.extend({
  livePhotoVideoId: z.uuidv4().nullish().describe('Live photo video ID'),
}).meta({ id: 'UpdateAssetDto' });

const AssetBulkDeleteSchema = BulkIdsSchema.extend({
  force: z.boolean().optional().describe('Force delete even if in use'),
}).meta({ id: 'AssetBulkDeleteDto' });

export const AssetIdsSchema = z
  .object({
    assetIds: z.array(z.uuidv4()).describe('Asset IDs'),
  })
  .meta({ id: 'AssetIdsDto' });

export enum AssetJobName {
  REFRESH_FACES = 'refresh-faces',
  REFRESH_METADATA = 'refresh-metadata',
  REGENERATE_THUMBNAIL = 'regenerate-thumbnail',
  TRANSCODE_VIDEO = 'transcode-video',
}

const AssetJobNameSchema = z.enum(AssetJobName).describe('Job name').meta({ id: 'AssetJobName' });

const AssetJobsSchema = AssetIdsSchema.extend({
  name: AssetJobNameSchema,
}).meta({ id: 'AssetJobsDto' });

const AssetStatsSchema = z
  .object({
    visibility: AssetVisibilitySchema.optional(),
    isFavorite: stringToBool.optional().describe('Filter by favorite status'),
    isTrashed: stringToBool.optional().describe('Filter by trash status'),
  })
  .meta({ id: 'AssetStatsDto' });

const AssetStatsResponseSchema = z
  .object({
    images: z.int().describe('Number of images'),
    videos: z.int().describe('Number of videos'),
    total: z.int().describe('Total number of assets'),
  })
  .meta({ id: 'AssetStatsResponseDto' });

const AssetMetadataRouteParamsSchema = z
  .object({
    id: z.uuidv4().describe('Asset ID'),
    key: z.string().describe('Metadata key'),
  })
  .meta({ id: 'AssetMetadataRouteParams' });

export const AssetMetadataUpsertItemSchema = z
  .object({
    key: z.string().describe('Metadata key'),
    value: z.record(z.string(), z.unknown()).describe('Metadata value (object)'),
  })
  .meta({ id: 'AssetMetadataUpsertItemDto' });

const AssetMetadataUpsertSchema = z
  .object({
    items: z.array(AssetMetadataUpsertItemSchema).describe('Metadata items to upsert'),
  })
  .meta({ id: 'AssetMetadataUpsertDto' });

const AssetMetadataBulkUpsertItemSchema = z
  .object({
    assetId: z.uuidv4().describe('Asset ID'),
    key: z.string().describe('Metadata key'),
    value: z.record(z.string(), z.unknown()).describe('Metadata value (object)'),
  })
  .meta({ id: 'AssetMetadataBulkUpsertItemDto' });

const AssetMetadataBulkUpsertSchema = z
  .object({
    items: z.array(AssetMetadataBulkUpsertItemSchema).describe('Metadata items to upsert'),
  })
  .meta({ id: 'AssetMetadataBulkUpsertDto' });

const AssetMetadataBulkDeleteItemSchema = z
  .object({
    assetId: z.uuidv4().describe('Asset ID'),
    key: z.string().describe('Metadata key'),
  })
  .meta({ id: 'AssetMetadataBulkDeleteItemDto' });

const AssetMetadataBulkDeleteSchema = z
  .object({
    items: z.array(AssetMetadataBulkDeleteItemSchema).describe('Metadata items to delete'),
  })
  .meta({ id: 'AssetMetadataBulkDeleteDto' });

const AssetMetadataResponseSchema = z
  .object({
    key: z.string().describe('Metadata key'),
    value: z.record(z.string(), z.unknown()).describe('Metadata value (object)'),
    updatedAt: isoDatetimeToDate.describe('Last update date'),
  })
  .meta({ id: 'AssetMetadataResponseDto' });

const AssetMetadataBulkResponseSchema = AssetMetadataResponseSchema.extend({
  assetId: z.uuidv4().describe('Asset ID'),
}).meta({ id: 'AssetMetadataBulkResponseDto' });

const AssetCopySchema = z
  .object({
    sourceId: z.uuidv4().describe('Source asset ID'),
    targetId: z.uuidv4().describe('Target asset ID'),
    sharedLinks: z.boolean().default(true).optional().describe('Copy shared links'),
    albums: z.boolean().default(true).optional().describe('Copy album associations'),
    sidecar: z.boolean().default(true).optional().describe('Copy sidecar file'),
    stack: z.boolean().default(true).optional().describe('Copy stack association'),
    favorite: z.boolean().default(true).optional().describe('Copy favorite status'),
  })
  .meta({ id: 'AssetCopyDto' });

const AssetDownloadOriginalSchema = z
  .object({
    edited: stringToBool.default(false).optional().describe('Return edited asset if available'),
  })
  .meta({ id: 'AssetDownloadOriginalDto' });

export const mapStats = (stats: AssetStats): AssetStatsResponseDto => {
  return {
    images: stats[AssetType.Image],
    videos: stats[AssetType.Video],
    total: Object.values(stats).reduce((total, value) => total + value, 0),
  };
};

export class AssetBulkUpdateDto extends createZodDto(AssetBulkUpdateSchema) {}
export class UpdateAssetDto extends createZodDto(UpdateAssetSchema) {}
export class AssetBulkDeleteDto extends createZodDto(AssetBulkDeleteSchema) {}
export class AssetIdsDto extends createZodDto(AssetIdsSchema) {}
export class AssetJobsDto extends createZodDto(AssetJobsSchema) {}
export class AssetStatsDto extends createZodDto(AssetStatsSchema) {}
export class AssetStatsResponseDto extends createZodDto(AssetStatsResponseSchema) {}
export class AssetMetadataRouteParams extends createZodDto(AssetMetadataRouteParamsSchema) {}
export class AssetMetadataUpsertDto extends createZodDto(AssetMetadataUpsertSchema) {}
export class AssetMetadataBulkUpsertDto extends createZodDto(AssetMetadataBulkUpsertSchema) {}
export class AssetMetadataBulkDeleteDto extends createZodDto(AssetMetadataBulkDeleteSchema) {}
export class AssetMetadataResponseDto extends createZodDto(AssetMetadataResponseSchema) {}
export class AssetMetadataBulkResponseDto extends createZodDto(AssetMetadataBulkResponseSchema) {}
export class AssetCopyDto extends createZodDto(AssetCopySchema) {}
export class AssetDownloadOriginalDto extends createZodDto(AssetDownloadOriginalSchema) {}
