import { z } from "zod";
import { describeRoute, resolver } from "hono-openapi";
import { backupWebhooksSchema } from "@zerobyte/core/backup-hooks";
import { publicVolumeSchema } from "@zerobyte/contracts/volumes";
import { repositorySchema } from "../repositories/repositories.dto";
import { backupProgressEventSchema } from "~/schemas/events-dto";

const retentionPolicySchema = z.object({
	keepLast: z.number().optional(),
	keepHourly: z.number().optional(),
	keepDaily: z.number().optional(),
	keepWeekly: z.number().optional(),
	keepMonthly: z.number().optional(),
	keepYearly: z.number().optional(),
	keepWithinDuration: z.string().optional(),
});

const backupScheduleSchema = z.object({
	id: z.number(),
	shortId: z.string(),
	name: z.string(),
	volumeId: z.number(),
	repositoryId: z.string(),
	enabled: z.boolean(),
	cronExpression: z.string(),
	retentionPolicy: retentionPolicySchema.nullable(),
	excludePatterns: z.array(z.string()).nullable(),
	excludeIfPresent: z.array(z.string()).nullable(),
	includePaths: z.array(z.string()).nullable(),
	includePatterns: z.array(z.string()).nullable(),
	oneFileSystem: z.boolean(),
	customResticParams: z.array(z.string()).nullable(),
	backupWebhooks: backupWebhooksSchema.nullable(),
	maxRetries: z.number(),
	retryDelay: z.number().transform((ms) => Math.round(ms / 60000)),
	lastBackupAt: z.number().nullable(),
	lastBackupStatus: z.enum(["success", "error", "in_progress", "warning"]).nullable(),
	lastBackupError: z.string().nullable(),
	nextBackupAt: z.number().nullable(),
	createdAt: z.number(),
	updatedAt: z.number(),
	volume: publicVolumeSchema,
	repository: repositorySchema,
});

const scheduleMirrorSchema = z.object({
	scheduleId: z.string(),
	repositoryId: z.string(),
	enabled: z.boolean(),
	lastCopyAt: z.number().nullable(),
	lastCopyStatus: z.enum(["success", "error", "in_progress"]).nullable(),
	lastCopyError: z.string().nullable(),
	createdAt: z.number(),
	repository: repositorySchema,
});

export const listBackupSchedulesResponse = backupScheduleSchema.array();

export type ListBackupSchedulesResponseDto = z.infer<typeof listBackupSchedulesResponse>;

export const listBackupSchedulesDto = describeRoute({
	description: "List all backup schedules",
	tags: ["Backups"],
	operationId: "listBackupSchedules",
	responses: {
		200: {
			description: "List of backup schedules",
			content: {
				"application/json": {
					schema: resolver(listBackupSchedulesResponse),
				},
			},
		},
	},
});

export const getBackupScheduleResponse = backupScheduleSchema;

export type GetBackupScheduleDto = z.infer<typeof getBackupScheduleResponse>;

export const getBackupScheduleDto = describeRoute({
	description: "Get a backup schedule by ID",
	tags: ["Backups"],
	operationId: "getBackupSchedule",
	responses: {
		200: {
			description: "Backup schedule details",
			content: {
				"application/json": {
					schema: resolver(getBackupScheduleResponse),
				},
			},
		},
	},
});

export const getBackupScheduleForVolumeResponse = backupScheduleSchema.nullable();

export type GetBackupScheduleForVolumeResponseDto = z.infer<typeof getBackupScheduleForVolumeResponse>;

export const getBackupScheduleForVolumeDto = describeRoute({
	description: "Get a backup schedule for a specific volume",
	tags: ["Backups"],
	operationId: "getBackupScheduleForVolume",
	responses: {
		200: {
			description: "Backup schedule details for the volume",
			content: {
				"application/json": {
					schema: resolver(getBackupScheduleForVolumeResponse),
				},
			},
		},
	},
});

export const createBackupScheduleBody = z.object({
	name: z.string().min(1).max(128),
	volumeId: z.union([z.string(), z.number()]),
	repositoryId: z.string(),
	enabled: z.boolean(),
	cronExpression: z.string(),
	retentionPolicy: retentionPolicySchema.optional(),
	excludePatterns: z.array(z.string()).optional(),
	excludeIfPresent: z.array(z.string()).optional(),
	includePaths: z.array(z.string()).optional(),
	includePatterns: z.array(z.string()).optional(),
	oneFileSystem: z.boolean().optional(),
	tags: z.array(z.string()).optional(),
	customResticParams: z.array(z.string()).optional(),
	backupWebhooks: backupWebhooksSchema.nullable().optional(),
	maxRetries: z.number().min(0).max(32).optional().default(2),
	retryDelay: z
		.number()
		.min(1)
		.max(1440)
		.optional()
		.default(15)
		.transform((minutes) => minutes * 60000),
});

export type CreateBackupScheduleBody = z.infer<typeof createBackupScheduleBody>;

export const createBackupScheduleResponse = backupScheduleSchema.omit({ volume: true, repository: true });

export type CreateBackupScheduleDto = z.infer<typeof createBackupScheduleResponse>;

export const createBackupScheduleDto = describeRoute({
	description: "Create a new backup schedule for a volume",
	operationId: "createBackupSchedule",
	tags: ["Backups"],
	responses: {
		201: {
			description: "Backup schedule created successfully",
			content: {
				"application/json": {
					schema: resolver(createBackupScheduleResponse),
				},
			},
		},
	},
});

export const updateBackupScheduleBody = z.object({
	name: z.string().min(1).max(128).optional(),
	repositoryId: z.string(),
	enabled: z.boolean().optional(),
	cronExpression: z.string(),
	retentionPolicy: retentionPolicySchema.optional(),
	excludePatterns: z.array(z.string()).optional(),
	excludeIfPresent: z.array(z.string()).optional(),
	includePaths: z.array(z.string()).optional(),
	includePatterns: z.array(z.string()).optional(),
	oneFileSystem: z.boolean().optional(),
	tags: z.array(z.string()).optional(),
	customResticParams: z.array(z.string()).optional(),
	backupWebhooks: backupWebhooksSchema.nullable().optional(),
	maxRetries: z.number().min(0).max(32).optional().default(2),
	retryDelay: z
		.number()
		.min(1)
		.max(1440)
		.optional()
		.default(15)
		.transform((minutes) => minutes * 60000),
});

export type UpdateBackupScheduleBody = z.infer<typeof updateBackupScheduleBody>;

export const updateBackupScheduleResponse = backupScheduleSchema.omit({ volume: true, repository: true });

export type UpdateBackupScheduleDto = z.infer<typeof updateBackupScheduleResponse>;

export const updateBackupScheduleDto = describeRoute({
	description: "Update a backup schedule",
	operationId: "updateBackupSchedule",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Backup schedule updated successfully",
			content: {
				"application/json": {
					schema: resolver(updateBackupScheduleResponse),
				},
			},
		},
	},
});

const deleteBackupScheduleResponse = z.object({
	success: z.boolean(),
});

export type DeleteBackupScheduleDto = z.infer<typeof deleteBackupScheduleResponse>;

export const deleteBackupScheduleDto = describeRoute({
	description: "Delete a backup schedule",
	operationId: "deleteBackupSchedule",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Backup schedule deleted successfully",
			content: {
				"application/json": {
					schema: resolver(deleteBackupScheduleResponse),
				},
			},
		},
	},
});

const runBackupNowResponse = z.object({
	success: z.boolean(),
});

export type RunBackupNowDto = z.infer<typeof runBackupNowResponse>;

export const runBackupNowDto = describeRoute({
	description: "Trigger a backup immediately for a schedule",
	operationId: "runBackupNow",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Backup started successfully",
			content: {
				"application/json": {
					schema: resolver(runBackupNowResponse),
				},
			},
		},
	},
});

const stopBackupResponse = z.object({
	success: z.boolean(),
});

export type StopBackupDto = z.infer<typeof stopBackupResponse>;

export const stopBackupDto = describeRoute({
	description: "Stop a backup that is currently in progress",
	operationId: "stopBackup",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Backup stopped successfully",
			content: {
				"application/json": {
					schema: resolver(stopBackupResponse),
				},
			},
		},
		409: {
			description: "No backup is currently running for this schedule",
		},
	},
});

const runForgetResponse = z.object({
	success: z.boolean(),
});

export type RunForgetDto = z.infer<typeof runForgetResponse>;

export const runForgetDto = describeRoute({
	description: "Manually apply retention policy to clean up old snapshots",
	operationId: "runForget",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Retention policy applied successfully",
			content: {
				"application/json": {
					schema: resolver(runForgetResponse),
				},
			},
		},
	},
});

const getScheduleMirrorsResponse = scheduleMirrorSchema.array();
export type GetScheduleMirrorsDto = z.infer<typeof getScheduleMirrorsResponse>;

export const getScheduleMirrorsDto = describeRoute({
	description: "Get mirror repository assignments for a backup schedule",
	operationId: "getScheduleMirrors",
	tags: ["Backups"],
	responses: {
		200: {
			description: "List of mirror repository assignments for the schedule",
			content: {
				"application/json": {
					schema: resolver(getScheduleMirrorsResponse),
				},
			},
		},
	},
});

export const updateScheduleMirrorsBody = z.object({
	mirrors: z
		.object({
			repositoryId: z.string(),
			enabled: z.boolean(),
		})
		.array(),
});

export type UpdateScheduleMirrorsBody = z.infer<typeof updateScheduleMirrorsBody>;

const updateScheduleMirrorsResponse = scheduleMirrorSchema.array();
export type UpdateScheduleMirrorsDto = z.infer<typeof updateScheduleMirrorsResponse>;

export const updateScheduleMirrorsDto = describeRoute({
	description: "Update mirror repository assignments for a backup schedule",
	operationId: "updateScheduleMirrors",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Mirror assignments updated successfully",
			content: {
				"application/json": {
					schema: resolver(updateScheduleMirrorsResponse),
				},
			},
		},
	},
});

const mirrorCompatibilitySchema = z.object({
	repositoryId: z.string(),
	compatible: z.boolean(),
	reason: z.string().nullable(),
});

const getMirrorCompatibilityResponse = mirrorCompatibilitySchema.array();
export type GetMirrorCompatibilityDto = z.infer<typeof getMirrorCompatibilityResponse>;

export const getMirrorCompatibilityDto = describeRoute({
	description: "Get mirror compatibility info for all repositories relative to a backup schedule's primary repository",
	operationId: "getMirrorCompatibility",
	tags: ["Backups"],
	responses: {
		200: {
			description: "List of repositories with their mirror compatibility status",
			content: {
				"application/json": {
					schema: resolver(getMirrorCompatibilityResponse),
				},
			},
		},
	},
});

export const reorderBackupSchedulesBody = z.object({
	scheduleShortIds: z.array(z.string()),
});

const reorderBackupSchedulesResponse = z.object({
	success: z.boolean(),
});

export type ReorderBackupSchedulesDto = z.infer<typeof reorderBackupSchedulesResponse>;

export const reorderBackupSchedulesDto = describeRoute({
	description: "Reorder backup schedules by providing an array of schedule short IDs in the desired order",
	operationId: "reorderBackupSchedules",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Backup schedules reordered successfully",
			content: {
				"application/json": {
					schema: resolver(reorderBackupSchedulesResponse),
				},
			},
		},
	},
});

const missingSnapshotSchema = z.object({
	short_id: z.string(),
	time: z.string(),
	size: z.number(),
});

const getMirrorSyncStatusResponse = z.object({
	sourceCount: z.number(),
	mirrorCount: z.number(),
	missingSnapshots: missingSnapshotSchema.array(),
});
export type GetMirrorSyncStatusDto = z.infer<typeof getMirrorSyncStatusResponse>;

export const getMirrorSyncStatusDto = describeRoute({
	description: "Get sync status for a specific mirror, including missing snapshots",
	operationId: "getMirrorSyncStatus",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Mirror sync status with missing snapshots",
			content: {
				"application/json": {
					schema: resolver(getMirrorSyncStatusResponse),
				},
			},
		},
	},
});

export const syncMirrorBody = z.object({
	snapshotIds: z.array(z.string()).optional(),
});

const syncMirrorResponse = z.object({
	success: z.boolean(),
});
export type SyncMirrorDto = z.infer<typeof syncMirrorResponse>;

export const syncMirrorDto = describeRoute({
	description: "Sync selected snapshots to a specific mirror repository",
	operationId: "syncMirror",
	tags: ["Backups"],
	responses: {
		200: {
			description: "Mirror sync started successfully",
			content: {
				"application/json": {
					schema: resolver(syncMirrorResponse),
				},
			},
		},
		409: {
			description: "Mirror is already syncing",
		},
	},
});

const getBackupProgressResponse = backupProgressEventSchema.nullable();
export type GetBackupProgressDto = z.infer<typeof getBackupProgressResponse>;

export const getBackupProgressDto = describeRoute({
	description:
		"Get the last known progress for a currently running backup. Returns null if no progress has been reported yet.",
	tags: ["Backup Schedules"],
	operationId: "getBackupProgress",
	responses: {
		200: {
			description: "Current backup progress or null if not yet available",
			content: {
				"application/json": {
					schema: resolver(getBackupProgressResponse),
				},
			},
		},
	},
});
