import { randomUUID } from 'crypto';
import sizeOf from 'image-size';
import { Response, Router } from 'express';
import type { Router as RouterType } from 'express';
import { ImageService, AliasService } from '@/services/core';
import { StorageFactory } from '@/services/StorageFactory';
import { asyncHandler } from '@/middleware/errorHandler';
import { validateApiKey } from '@/middleware/auth/validateApiKey';
import { requireProjectKey } from '@/middleware/auth/requireProjectKey';
import { rateLimitByApiKey } from '@/middleware/auth/rateLimiter';
import { uploadSingleImage, handleUploadErrors } from '@/middleware/upload';
import { validateAndNormalizePagination } from '@/utils/validators';
import { buildPaginatedResponse } from '@/utils/helpers';
import { toImageResponse } from '@/types/responses';
import { db } from '@/db';
import { flows, type Image } from '@banatie/database';
import { eq } from 'drizzle-orm';
import type {
  UploadImageResponse,
  ListImagesResponse,
  GetImageResponse,
  UpdateImageResponse,
  DeleteImageResponse,
  ResolveAliasResponse,
} from '@/types/responses';

export const imagesRouter: RouterType = Router();

let imageService: ImageService;
let aliasService: AliasService;

const getImageService = (): ImageService => {
  if (!imageService) {
    imageService = new ImageService();
  }
  return imageService;
};

const getAliasService = (): AliasService => {
  if (!aliasService) {
    aliasService = new AliasService();
  }
  return aliasService;
};

/**
 * Resolve id_or_alias parameter to imageId
 * Supports both UUID and alias (@-prefixed) identifiers
 * Per Section 6.2 of api-refactoring-final.md
 *
 * @param identifier - UUID or alias string
 * @param projectId - Project ID for alias resolution
 * @param flowId - Optional flow ID for flow-scoped alias resolution
 * @returns imageId (UUID)
 * @throws Error if alias not found
 */
async function resolveImageIdentifier(
  identifier: string,
  projectId: string,
  flowId?: string
): Promise<string> {
  // Check if parameter is alias (starts with @)
  if (identifier.startsWith('@')) {
    const aliasServiceInstance = getAliasService();
    const resolution = await aliasServiceInstance.resolve(
      identifier,
      projectId,
      flowId
    );

    if (!resolution) {
      throw new Error(`Alias '${identifier}' not found`);
    }

    return resolution.imageId;
  }

  // Otherwise treat as UUID
  return identifier;
}

/**
 * Upload a single image file to project storage
 *
 * Uploads an image file to MinIO storage and creates a database record with support for:
 * - Lazy flow creation using pendingFlowId when flowId is undefined
 * - Eager flow creation when flowAlias is provided
 * - Project-scoped alias assignment
 * - Custom metadata storage
 * - Multiple file formats (JPEG, PNG, WebP, etc.)
 *
 * FlowId behavior:
 * - undefined (not provided) → generates pendingFlowId, defers flow creation (lazy)
 * - null (explicitly null) → no flow association
 * - string (specific value) → uses provided flow ID, creates if needed
 *
 * @route POST /api/v1/images/upload
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {File} req.file - Image file (multipart/form-data, max 5MB)
 * @param {string} [req.body.alias] - Project-scoped alias (@custom-name)
 * @param {string|null} [req.body.flowId] - Flow association (undefined=auto, null=none, string=specific)
 * @param {string} [req.body.flowAlias] - Flow-scoped alias (requires flowId, triggers eager creation)
 * @param {string} [req.body.meta] - Custom metadata (JSON string)
 *
 * @returns {UploadImageResponse} 201 - Uploaded image with storage details
 * @returns {object} 400 - Missing file or validation error
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 413 - File too large
 * @returns {object} 415 - Unsupported file type
 * @returns {object} 429 - Rate limit exceeded
 * @returns {object} 500 - Upload or storage error
 *
 * @throws {Error} VALIDATION_ERROR - No file provided
 * @throws {Error} UPLOAD_ERROR - File upload failed
 * @throws {Error} ALIAS_CONFLICT - Alias already exists
 *
 * @example
 * // Upload with automatic flow creation
 * POST /api/v1/images/upload
 * Content-Type: multipart/form-data
 * { file: <image.jpg>, alias: "@hero-bg" }
 *
 * @example
 * // Upload with eager flow creation and flow alias
 * POST /api/v1/images/upload
 * { file: <image.jpg>, flowAlias: "@step-1" }
 */
imagesRouter.post(
  '/upload',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  uploadSingleImage,
  handleUploadErrors,
  asyncHandler(async (req: any, res: Response<UploadImageResponse>) => {
    const service = getImageService();
    const { alias, flowId, flowAlias, meta } = req.body;

    if (!req.file) {
      res.status(400).json({
        success: false,
        error: {
          message: 'No file provided',
          code: 'VALIDATION_ERROR',
        },
      });
      return;
    }

    const projectId = req.apiKey.projectId;
    const apiKeyId = req.apiKey.id;
    const orgId = req.apiKey.organizationSlug || 'default';
    const projectSlug = req.apiKey.projectSlug;
    const file = req.file;

    // FlowId logic (matching GenerationService lazy pattern):
    // - If undefined → generate UUID for pendingFlowId, flowId = null (lazy)
    // - If null → flowId = null, pendingFlowId = null (explicitly no flow)
    // - If string → flowId = string, pendingFlowId = null (use provided, create if needed)
    let finalFlowId: string | null;
    let pendingFlowId: string | null = null;

    if (flowId === undefined) {
      // Lazy pattern: defer flow creation until needed
      pendingFlowId = randomUUID();
      finalFlowId = null;
    } else if (flowId === null) {
      // Explicitly no flow
      finalFlowId = null;
      pendingFlowId = null;
    } else {
      // Specific flowId provided - ensure flow exists (eager creation)
      // Use flowId directly since TypeScript has narrowed it to string in this branch
      const providedFlowId = flowId;
      finalFlowId = providedFlowId;
      pendingFlowId = null;

      // Check if flow exists, create if not
      const existingFlow = await db.query.flows.findFirst({
        where: eq(flows.id, providedFlowId),
      });

      if (!existingFlow) {
        await db.insert(flows).values({
          id: providedFlowId,
          projectId,
          aliases: {},
          meta: {},
        });

        // Link any pending images to this new flow
        await service.linkPendingImagesToFlow(providedFlowId, projectId);
      }
    }

    try {
      const storageService = await StorageFactory.getInstance();
      const imageId = randomUUID();

      const uploadResult = await storageService.uploadFile({
        orgSlug: orgId,
        projectSlug,
        imageId,
        buffer: file.buffer,
        contentType: file.mimetype,
        originalFilename: file.originalname,
      });

      if (!uploadResult.success) {
        res.status(500).json({
          success: false,
          error: {
            message: 'File upload failed',
            code: 'UPLOAD_ERROR',
            details: uploadResult.error,
          },
        });
        return;
      }

      // Extract image dimensions from uploaded file buffer
      let width: number | null = null;
      let height: number | null = null;
      try {
        const dimensions = sizeOf(file.buffer);
        if (dimensions.width && dimensions.height) {
          width = dimensions.width;
          height = dimensions.height;
        }
      } catch (error) {
        console.warn('Failed to extract image dimensions:', error);
      }

      const imageRecord = await service.create({
        projectId,
        flowId: finalFlowId,
        pendingFlowId: pendingFlowId,
        generationId: null,
        apiKeyId,
        storageKey: uploadResult.path!,
        storageUrl: uploadResult.url!,
        mimeType: file.mimetype,
        fileSize: file.size,
        fileHash: null,
        source: 'uploaded',
        alias: null,
        meta: meta ? JSON.parse(meta) : {},
        width,
        height,
      });

      // Reassign project alias if provided (override behavior per Section 5.2)
      if (alias) {
        await service.reassignProjectAlias(alias, imageRecord.id, projectId);
      }

      // Eager flow creation if flowAlias is provided
      if (flowAlias) {
        // Use pendingFlowId if available, otherwise finalFlowId
        const flowIdToUse = pendingFlowId || finalFlowId;

        if (!flowIdToUse) {
          throw new Error('Cannot create flow: no flowId available');
        }

        // Check if flow exists, create if not
        const existingFlow = await db.query.flows.findFirst({
          where: eq(flows.id, flowIdToUse),
        });

        if (!existingFlow) {
          await db.insert(flows).values({
            id: flowIdToUse,
            projectId,
            aliases: {},
            meta: {},
          });

          // Link pending images if this was a lazy flow
          if (pendingFlowId) {
            await service.linkPendingImagesToFlow(flowIdToUse, projectId);
          }
        }

        // Assign flow alias to uploaded image
        const flow = await db.query.flows.findFirst({
          where: eq(flows.id, flowIdToUse),
        });

        if (flow) {
          const currentAliases = (flow.aliases as Record<string, string>) || {};
          const updatedAliases = { ...currentAliases };
          updatedAliases[flowAlias] = imageRecord.id;

          await db
            .update(flows)
            .set({ aliases: updatedAliases, updatedAt: new Date() })
            .where(eq(flows.id, flowIdToUse));
        }
      }

      // Refetch image to include any updates (alias assignment, flow alias)
      const finalImage = await service.getById(imageRecord.id);

      res.status(201).json({
        success: true,
        data: toImageResponse(finalImage!),
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Upload failed',
          code: 'UPLOAD_ERROR',
        },
      });
      return;
    }
  })
);

/**
 * List all images for the project with filtering and pagination
 *
 * Retrieves images (both generated and uploaded) with support for:
 * - Flow-based filtering
 * - Source filtering (generated vs uploaded)
 * - Alias filtering (exact match)
 * - Pagination with configurable limit and offset
 * - Optional inclusion of soft-deleted images
 *
 * @route GET /api/v1/images
 * @authentication Project Key required
 *
 * @param {string} [req.query.flowId] - Filter by flow ID
 * @param {string} [req.query.source] - Filter by source (generated|uploaded)
 * @param {string} [req.query.alias] - Filter by exact alias match
 * @param {number} [req.query.limit=20] - Results per page (max 100)
 * @param {number} [req.query.offset=0] - Number of results to skip
 * @param {boolean} [req.query.includeDeleted=false] - Include soft-deleted images
 *
 * @returns {ListImagesResponse} 200 - Paginated list of images
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * // List uploaded images in a flow
 * GET /api/v1/images?flowId=abc-123&source=uploaded&limit=50
 */
imagesRouter.get(
  '/',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListImagesResponse>) => {
    const service = getImageService();
    const { flowId, source, alias, limit, offset, includeDeleted } = req.query;

    const paginationResult = validateAndNormalizePagination(limit, offset);
    if (!paginationResult.valid) {
      res.status(400).json({
        success: false,
        data: [],
        pagination: { total: 0, limit: 20, offset: 0, hasMore: false },
      });
      return;
    }

    const { limit: validatedLimit, offset: validatedOffset } = paginationResult.params!;
    const projectId = req.apiKey.projectId;

    const result = await service.list(
      {
        projectId,
        flowId: flowId as string | undefined,
        source: source as 'generated' | 'uploaded' | undefined,
        alias: alias as string | undefined,
        deleted: includeDeleted === 'true' ? true : undefined,
      },
      validatedLimit,
      validatedOffset
    );

    const responseData = result.images.map((img) => toImageResponse(img));

    res.json(
      buildPaginatedResponse(responseData, result.total, validatedLimit, validatedOffset)
    );
  })
);

/**
 * @deprecated Use GET /api/v1/images/:alias directly instead (Section 6.2)
 *
 * Resolve an alias to an image using 3-tier precedence system
 *
 * **DEPRECATED**: This endpoint is deprecated as of Section 6.2. Use the main
 * GET /api/v1/images/:id_or_alias endpoint instead, which supports both UUIDs
 * and aliases (@-prefixed) directly in the path parameter.
 *
 * **Migration Guide**:
 * - Old: GET /api/v1/images/resolve/@hero
 * - New: GET /api/v1/images/@hero
 *
 * This endpoint remains functional for backwards compatibility but will be
 * removed in a future version.
 *
 * Resolves aliases through a priority-based lookup system:
 * 1. Technical aliases (@last, @first, @upload) - computed on-the-fly
 * 2. Flow-scoped aliases - looked up in flow's JSONB aliases field (requires flowId)
 * 3. Project-scoped aliases - looked up in images.alias column
 *
 * Returns the image ID, resolution scope, and complete image details.
 *
 * @route GET /api/v1/images/resolve/:alias
 * @authentication Project Key required
 *
 * @param {string} req.params.alias - Alias to resolve (e.g., "@last", "@hero", "@step-1")
 * @param {string} [req.query.flowId] - Flow context for flow-scoped resolution
 *
 * @returns {ResolveAliasResponse} 200 - Resolved image with scope and details (includes X-Deprecated header)
 * @returns {object} 404 - Alias not found in any scope
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} ALIAS_NOT_FOUND - Alias does not exist
 * @throws {Error} RESOLUTION_ERROR - Resolution failed
 *
 * @example
 * // Resolve technical alias
 * GET /api/v1/images/resolve/@last
 *
 * @example
 * // Resolve flow-scoped alias
 * GET /api/v1/images/resolve/@step-1?flowId=abc-123
 *
 * @example
 * // Resolve project-scoped alias
 * GET /api/v1/images/resolve/@hero-bg
 */
imagesRouter.get(
  '/resolve/:alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ResolveAliasResponse>) => {
    const aliasServiceInstance = getAliasService();
    const { alias } = req.params;
    const { flowId } = req.query;

    const projectId = req.apiKey.projectId;

    // Add deprecation header
    res.setHeader(
      'X-Deprecated',
      'This endpoint is deprecated. Use GET /api/v1/images/:alias instead (Section 6.2)'
    );

    try {
      const resolution = await aliasServiceInstance.resolve(
        alias,
        projectId,
        flowId as string | undefined
      );

      if (!resolution) {
        res.status(404).json({
          success: false,
          error: {
            message: `Alias '${alias}' not found`,
            code: 'ALIAS_NOT_FOUND',
          },
        });
        return;
      }

      // Verify project ownership
      if (resolution.image && resolution.image.projectId !== projectId) {
        res.status(404).json({
          success: false,
          error: {
            message: 'Alias not found',
            code: 'ALIAS_NOT_FOUND',
          },
        });
        return;
      }

      res.json({
        success: true,
        data: {
          alias,
          imageId: resolution.imageId,
          scope: resolution.scope,
          flowId: resolution.flowId,
          image: resolution.image ? toImageResponse(resolution.image) : ({} as any),
        },
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Failed to resolve alias',
          code: 'RESOLUTION_ERROR',
        },
      });
      return;
    }
  })
);

/**
 * Get a single image by ID with complete details
 *
 * Retrieves full image information including:
 * - Storage URLs and keys
 * - Project and flow associations
 * - Alias assignments (project-scoped)
 * - Source (generated vs uploaded)
 * - File metadata (size, MIME type, hash)
 * - Focal point and custom metadata
 *
 * @route GET /api/v1/images/:id_or_alias
 * @authentication Project Key required
 *
 * @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@alias)
 * @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
 *
 * @returns {GetImageResponse} 200 - Complete image details
 * @returns {object} 404 - Image not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} IMAGE_NOT_FOUND - Image does not exist
 *
 * @example
 * GET /api/v1/images/550e8400-e29b-41d4-a716-446655440000
 * GET /api/v1/images/@hero
 * GET /api/v1/images/@hero?flowId=abc-123
 */
imagesRouter.get(
  '/:id_or_alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<GetImageResponse>) => {
    const service = getImageService();
    const { id_or_alias } = req.params;
    const { flowId } = req.query;

    // Resolve alias to imageId if needed (Section 6.2)
    let imageId: string;
    try {
      imageId = await resolveImageIdentifier(
        id_or_alias,
        req.apiKey.projectId,
        flowId as string | undefined
      );
    } catch (error) {
      res.status(404).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    const image = await service.getById(imageId);
    if (!image) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    if (image.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    res.json({
      success: true,
      data: toImageResponse(image),
    });
  })
);

/**
 * Update image metadata (focal point and custom metadata)
 *
 * Updates non-generative image properties:
 * - Focal point for image cropping (x, y coordinates 0.0-1.0)
 * - Custom metadata (arbitrary JSON object)
 *
 * Note: Alias assignment moved to separate endpoint PUT /images/:id/alias (Section 6.1)
 * Supports both UUID and alias (@-prefixed) identifiers per Section 6.2.
 *
 * @route PUT /api/v1/images/:id_or_alias
 * @authentication Project Key required
 *
 * @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@-prefixed)
 * @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
 * @param {UpdateImageRequest} req.body - Update parameters
 * @param {object} [req.body.focalPoint] - Focal point for cropping
 * @param {number} req.body.focalPoint.x - X coordinate (0.0-1.0)
 * @param {number} req.body.focalPoint.y - Y coordinate (0.0-1.0)
 * @param {object} [req.body.meta] - Custom metadata
 *
 * @returns {UpdateImageResponse} 200 - Updated image details
 * @returns {object} 404 - Image not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} IMAGE_NOT_FOUND - Image does not exist
 *
 * @example UUID identifier
 * PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000
 * {
 *   "focalPoint": { "x": 0.5, "y": 0.3 },
 *   "meta": { "category": "hero", "priority": 1 }
 * }
 *
 * @example Project-scoped alias
 * PUT /api/v1/images/@hero-banner
 * {
 *   "focalPoint": { "x": 0.5, "y": 0.3 }
 * }
 *
 * @example Flow-scoped alias
 * PUT /api/v1/images/@product-shot?flowId=123e4567-e89b-12d3-a456-426614174000
 * {
 *   "meta": { "category": "product" }
 * }
 */
imagesRouter.put(
  '/:id_or_alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<UpdateImageResponse>) => {
    const service = getImageService();
    const { id_or_alias } = req.params;
    const { flowId } = req.query;
    const { focalPoint, meta } = req.body; // Removed alias (Section 6.1)

    // Resolve alias to imageId if needed (Section 6.2)
    let imageId: string;
    try {
      imageId = await resolveImageIdentifier(
        id_or_alias,
        req.apiKey.projectId,
        flowId as string | undefined
      );
    } catch (error) {
      res.status(404).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    const image = await service.getById(imageId);
    if (!image) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    if (image.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    const updates: {
      focalPoint?: { x: number; y: number };
      meta?: Record<string, unknown>;
    } = {};

    if (focalPoint !== undefined) updates.focalPoint = focalPoint;
    if (meta !== undefined) updates.meta = meta;

    const updated = await service.update(imageId, updates);

    res.json({
      success: true,
      data: toImageResponse(updated),
    });
  })
);

/**
 * Assign or remove a project-scoped alias from an image
 *
 * Sets, updates, or removes the project-scoped alias for an image:
 * - Alias must start with @ symbol (when assigning)
 * - Must be unique within the project
 * - Replaces existing alias if image already has one
 * - Used for alias resolution in generations and CDN access
 * - Set alias to null to remove existing alias
 *
 * This is a dedicated endpoint introduced in Section 6.1 to separate
 * alias assignment from general metadata updates.
 * Supports both UUID and alias (@-prefixed) identifiers per Section 6.2.
 *
 * @route PUT /api/v1/images/:id_or_alias/alias
 * @authentication Project Key required
 *
 * @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@-prefixed)
 * @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
 * @param {object} req.body - Request body
 * @param {string|null} req.body.alias - Project-scoped alias (e.g., "@hero-bg") or null to remove
 *
 * @returns {UpdateImageResponse} 200 - Updated image with new/removed alias
 * @returns {object} 404 - Image not found or access denied
 * @returns {object} 400 - Invalid alias format
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 409 - Alias already exists
 *
 * @throws {Error} IMAGE_NOT_FOUND - Image does not exist
 * @throws {Error} VALIDATION_ERROR - Invalid alias format
 * @throws {Error} ALIAS_CONFLICT - Alias already assigned to another image
 *
 * @example Assign alias
 * PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000/alias
 * {
 *   "alias": "@hero-background"
 * }
 *
 * @example Remove alias
 * PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000/alias
 * {
 *   "alias": null
 * }
 *
 * @example Project-scoped alias identifier
 * PUT /api/v1/images/@old-hero/alias
 * {
 *   "alias": "@new-hero"
 * }
 *
 * @example Flow-scoped alias identifier
 * PUT /api/v1/images/@temp-product/alias?flowId=123e4567-e89b-12d3-a456-426614174000
 * {
 *   "alias": "@final-product"
 * }
 */
imagesRouter.put(
  '/:id_or_alias/alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<UpdateImageResponse>) => {
    const service = getImageService();
    const { id_or_alias } = req.params;
    const { flowId } = req.query;
    const { alias } = req.body;

    // Validate: alias must be null (to remove) or a non-empty string
    if (alias !== null && (typeof alias !== 'string' || alias.trim() === '')) {
      res.status(400).json({
        success: false,
        error: {
          message: 'Alias must be null (to remove) or a non-empty string',
          code: 'VALIDATION_ERROR',
        },
      });
      return;
    }

    // Resolve alias to imageId if needed (Section 6.2)
    let imageId: string;
    try {
      imageId = await resolveImageIdentifier(
        id_or_alias,
        req.apiKey.projectId,
        flowId as string | undefined
      );
    } catch (error) {
      res.status(404).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    const image = await service.getById(imageId);
    if (!image) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    if (image.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    // Either remove alias (null) or assign new one (override behavior per Section 5.2)
    let updated: Image;
    if (alias === null) {
      // Remove alias
      updated = await service.update(imageId, { alias: null });
    } else {
      // Reassign alias (clears from any existing image, then assigns to this one)
      await service.reassignProjectAlias(alias, imageId, image.projectId);
      updated = (await service.getById(imageId))!;
    }

    res.json({
      success: true,
      data: toImageResponse(updated),
    });
  })
);

/**
 * Delete an image with storage cleanup and cascading deletions
 *
 * Performs hard delete of image record and MinIO file with cascading operations:
 * - Deletes image record from database (hard delete, no soft delete)
 * - Removes file from MinIO storage permanently
 * - Cascades to delete generation-image relationships
 * - Removes image from flow aliases (if present)
 * - Cannot be undone
 *
 * Use with caution: This is a destructive operation that permanently removes
 * the image file and all database references.
 * Supports both UUID and alias (@-prefixed) identifiers per Section 6.2.
 *
 * @route DELETE /api/v1/images/:id_or_alias
 * @authentication Project Key required
 *
 * @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@-prefixed)
 * @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
 *
 * @returns {DeleteImageResponse} 200 - Deletion confirmation with image ID
 * @returns {object} 404 - Image not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} IMAGE_NOT_FOUND - Image does not exist
 *
 * @example UUID identifier
 * DELETE /api/v1/images/550e8400-e29b-41d4-a716-446655440000
 *
 * Response:
 * {
 *   "success": true,
 *   "data": { "id": "550e8400-e29b-41d4-a716-446655440000" }
 * }
 *
 * @example Project-scoped alias
 * DELETE /api/v1/images/@old-banner
 *
 * @example Flow-scoped alias
 * DELETE /api/v1/images/@temp-image?flowId=123e4567-e89b-12d3-a456-426614174000
 */
imagesRouter.delete(
  '/:id_or_alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<DeleteImageResponse>) => {
    const service = getImageService();
    const { id_or_alias } = req.params;
    const { flowId } = req.query;

    // Resolve alias to imageId if needed (Section 6.2)
    let imageId: string;
    try {
      imageId = await resolveImageIdentifier(
        id_or_alias,
        req.apiKey.projectId,
        flowId as string | undefined
      );
    } catch (error) {
      res.status(404).json({
        success: false,
        error: {
          message: error instanceof Error ? error.message : 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    const image = await service.getById(imageId);
    if (!image) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    if (image.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Image not found',
          code: 'IMAGE_NOT_FOUND',
        },
      });
      return;
    }

    await service.hardDelete(imageId);

    res.json({
      success: true,
      data: { id: imageId },
    });
  })
);
