import { Response, Router } from 'express';
import type { Router as RouterType } from 'express';
import { GenerationService } from '@/services/core';
import { asyncHandler } from '@/middleware/errorHandler';
import { validateApiKey } from '@/middleware/auth/validateApiKey';
import { requireProjectKey } from '@/middleware/auth/requireProjectKey';
import { rateLimitByApiKey } from '@/middleware/auth/rateLimiter';
import { autoEnhancePrompt } from '@/middleware/promptEnhancement';
import { validateAndNormalizePagination } from '@/utils/validators';
import { buildPaginatedResponse } from '@/utils/helpers';
import { toGenerationResponse } from '@/types/responses';
import type {
  CreateGenerationResponse,
  ListGenerationsResponse,
  GetGenerationResponse,
} from '@/types/responses';

export const generationsRouter: RouterType = Router();

let generationService: GenerationService;

const getGenerationService = (): GenerationService => {
  if (!generationService) {
    generationService = new GenerationService();
  }
  return generationService;
};

/**
 * Create a new image generation from a text prompt
 *
 * Generates AI-powered images using Gemini Flash Image model with support for:
 * - Text prompts with optional auto-enhancement
 * - Reference images for style/context
 * - Flow association and flow-scoped aliases
 * - Project-scoped aliases for direct access
 * - Custom metadata storage
 *
 * @route POST /api/v1/generations
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {CreateGenerationRequest} req.body - Generation parameters
 * @param {string} req.body.prompt - Text description of desired image (required)
 * @param {string[]} [req.body.referenceImages] - Array of aliases to use as references
 * @param {string} [req.body.aspectRatio='1:1'] - Aspect ratio (1:1, 16:9, 3:2, 9:16)
 * @param {string} [req.body.flowId] - Associate with existing flow
 * @param {string} [req.body.alias] - Project-scoped alias (@custom-name)
 * @param {string} [req.body.flowAlias] - Flow-scoped alias (requires flowId)
 * @param {boolean} [req.body.autoEnhance=true] - Enable prompt enhancement
 * @param {object} [req.body.meta] - Custom metadata
 *
 * @returns {CreateGenerationResponse} 201 - Generation created with status
 * @returns {object} 400 - Invalid request parameters
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} VALIDATION_ERROR - Missing or invalid prompt
 * @throws {Error} ALIAS_CONFLICT - Alias already exists
 * @throws {Error} FLOW_NOT_FOUND - Flow ID does not exist
 * @throws {Error} IMAGE_NOT_FOUND - Reference image alias not found
 *
 * @example
 * // Basic generation
 * POST /api/v1/generations
 * {
 *   "prompt": "A serene mountain landscape at sunset",
 *   "aspectRatio": "16:9"
 * }
 *
 * @example
 * // With reference images and alias
 * POST /api/v1/generations
 * {
 *   "prompt": "Product photo in this style",
 *   "referenceImages": ["@brand-style", "@product-template"],
 *   "alias": "@hero-image",
 *   "autoEnhance": true
 * }
 */
generationsRouter.post(
  '/',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  autoEnhancePrompt,
  asyncHandler(async (req: any, res: Response<CreateGenerationResponse>) => {
    const service = getGenerationService();

    // Extract original prompt from middleware property if enhancement was attempted
    // Otherwise fall back to request body
    const prompt = req.originalPrompt || req.body.prompt;

    const {
      referenceImages,
      aspectRatio,
      flowId,
      alias,
      flowAlias,
      autoEnhance,
      meta,
    } = req.body;

    if (!prompt || typeof prompt !== 'string') {
      res.status(400).json({
        success: false,
        error: {
          message: 'Prompt is required and must be a string',
          code: 'VALIDATION_ERROR',
        },
      });
      return;
    }

    const projectId = req.apiKey.projectId;
    const apiKeyId = req.apiKey.id;
    const organizationSlug = req.apiKey.organizationSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
    const projectSlug = req.apiKey.projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main';

    const generation = await service.create({
      projectId,
      apiKeyId,
      organizationSlug,
      projectSlug,
      prompt,
      referenceImages,
      aspectRatio,
      flowId,
      alias,
      flowAlias,
      autoEnhance,
      enhancedPrompt: req.enhancedPrompt,
      meta,
      requestId: req.requestId,
    });

    res.status(201).json({
      success: true,
      data: toGenerationResponse(generation),
    });
  })
);

/**
 * List all generations for the project with filtering and pagination
 *
 * Retrieves generations with support for:
 * - Flow-based filtering
 * - Status filtering (pending, processing, success, failed)
 * - Pagination with configurable limit and offset
 * - Optional inclusion of soft-deleted generations
 *
 * @route GET /api/v1/generations
 * @authentication Project Key required
 *
 * @param {string} [req.query.flowId] - Filter by flow ID
 * @param {string} [req.query.status] - Filter by status (pending|processing|success|failed)
 * @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 generations
 *
 * @returns {ListGenerationsResponse} 200 - Paginated list of generations
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * // List recent generations
 * GET /api/v1/generations?limit=10&offset=0
 *
 * @example
 * // Filter by flow and status
 * GET /api/v1/generations?flowId=abc-123&status=success&limit=50
 */
generationsRouter.get(
  '/',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListGenerationsResponse>) => {
    const service = getGenerationService();
    const { flowId, status, 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,
        status: status as 'pending' | 'processing' | 'success' | 'failed' | undefined,
        deleted: includeDeleted === 'true' ? true : undefined,
      },
      validatedLimit,
      validatedOffset
    );

    const responseData = result.generations.map((gen) => toGenerationResponse(gen));

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

/**
 * Get a single generation by ID with full details
 *
 * Retrieves complete generation information including:
 * - Generation status and metadata
 * - Output image details (URL, dimensions, etc.)
 * - Reference images used
 * - Flow association
 * - Timestamps and audit trail
 *
 * @route GET /api/v1/generations/:id
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Generation ID (UUID)
 *
 * @returns {GetGenerationResponse} 200 - Complete generation details
 * @returns {object} 404 - Generation not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} GENERATION_NOT_FOUND - Generation does not exist
 *
 * @example
 * GET /api/v1/generations/550e8400-e29b-41d4-a716-446655440000
 */
generationsRouter.get(
  '/:id',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<GetGenerationResponse>) => {
    const service = getGenerationService();
    const { id } = req.params;

    const generation = await service.getByIdWithRelations(id);

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

    res.json({
      success: true,
      data: toGenerationResponse(generation),
    });
  })
);

/**
 * Update generation parameters with automatic regeneration
 *
 * Updates generation settings with intelligent regeneration behavior:
 * - Changing prompt or aspectRatio triggers automatic regeneration
 * - Changing flowId or meta updates metadata only (no regeneration)
 * - Regeneration replaces existing output image (same ID and URLs)
 * - All changes preserve generation history and IDs
 *
 * @route PUT /api/v1/generations/:id
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.id - Generation ID (UUID)
 * @param {UpdateGenerationRequest} req.body - Update parameters
 * @param {string} [req.body.prompt] - New prompt (triggers regeneration)
 * @param {string} [req.body.aspectRatio] - New aspect ratio (triggers regeneration)
 * @param {string|null} [req.body.flowId] - Change flow association (null to detach)
 * @param {object} [req.body.meta] - Update custom metadata
 *
 * @returns {GetGenerationResponse} 200 - Updated generation with new output
 * @returns {object} 404 - Generation not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} GENERATION_NOT_FOUND - Generation does not exist
 * @throws {Error} FLOW_NOT_FOUND - New flow ID does not exist
 *
 * @example
 * // Update prompt (triggers regeneration)
 * PUT /api/v1/generations/550e8400-e29b-41d4-a716-446655440000
 * {
 *   "prompt": "Updated: A mountain landscape with vibrant colors"
 * }
 *
 * @example
 * // Change flow association (no regeneration)
 * PUT /api/v1/generations/550e8400-e29b-41d4-a716-446655440000
 * {
 *   "flowId": "new-flow-id-123"
 * }
 */
generationsRouter.put(
  '/:id',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<GetGenerationResponse>) => {
    const service = getGenerationService();
    const { id } = req.params;
    const { prompt, aspectRatio, flowId, meta } = req.body;

    const original = await service.getById(id);
    if (!original) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Generation not found',
          code: 'GENERATION_NOT_FOUND',
        },
      });
      return;
    }

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

    const updated = await service.update(id, {
      prompt,
      aspectRatio,
      flowId,
      meta,
    });

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

/**
 * Regenerate existing generation with exact same parameters
 *
 * Creates a new image using the original generation parameters:
 * - Uses exact same prompt, aspect ratio, and reference images
 * - Works regardless of current status (success, failed, pending)
 * - Replaces existing output image (preserves ID and URLs)
 * - No parameter modifications allowed (use PUT for changes)
 * - Useful for refreshing stale images or recovering from failures
 *
 * @route POST /api/v1/generations/:id/regenerate
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.id - Generation ID (UUID)
 *
 * @returns {GetGenerationResponse} 200 - Regenerated generation with new output
 * @returns {object} 404 - Generation not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} GENERATION_NOT_FOUND - Generation does not exist
 *
 * @example
 * POST /api/v1/generations/550e8400-e29b-41d4-a716-446655440000/regenerate
 */
generationsRouter.post(
  '/:id/regenerate',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<GetGenerationResponse>) => {
    const service = getGenerationService();
    const { id } = req.params;

    const original = await service.getById(id);
    if (!original) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Generation not found',
          code: 'GENERATION_NOT_FOUND',
        },
      });
      return;
    }

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

    const regenerated = await service.regenerate(id);

    res.json({
      success: true,
      data: toGenerationResponse(regenerated),
    });
  })
);

/**
 * Retry a failed generation (legacy endpoint)
 *
 * @deprecated Use POST /api/v1/generations/:id/regenerate instead
 *
 * This endpoint is maintained for backward compatibility and delegates
 * to the regenerate endpoint. New integrations should use /regenerate.
 *
 * @route POST /api/v1/generations/:id/retry
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.id - Generation ID (UUID)
 *
 * @returns {CreateGenerationResponse} 201 - Regenerated generation
 * @returns {object} 404 - Generation not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @see POST /api/v1/generations/:id/regenerate - Preferred endpoint
 */
generationsRouter.post(
  '/:id/retry',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<CreateGenerationResponse>) => {
    const service = getGenerationService();
    const { id } = req.params;

    const original = await service.getById(id);
    if (!original) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Generation not found',
          code: 'GENERATION_NOT_FOUND',
        },
      });
      return;
    }

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

    const regenerated = await service.regenerate(id);

    res.status(201).json({
      success: true,
      data: toGenerationResponse(regenerated),
    });
  })
);

/**
 * Delete a generation and conditionally its output image (Section 7.2)
 *
 * Performs deletion with alias protection:
 * - Hard delete generation record (permanently removed from database)
 * - If output image has NO project alias: hard delete image with MinIO cleanup
 * - If output image HAS project alias: keep image, set generationId=NULL
 *
 * Rationale: Images with aliases are used as standalone assets and should be preserved.
 * Images without aliases were created only for this generation and can be deleted together.
 *
 * @route DELETE /api/v1/generations/:id
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Generation ID (UUID)
 *
 * @returns {object} 200 - Deletion confirmation with generation ID
 * @returns {object} 404 - Generation not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} GENERATION_NOT_FOUND - Generation does not exist
 *
 * @example
 * DELETE /api/v1/generations/550e8400-e29b-41d4-a716-446655440000
 *
 * Response:
 * {
 *   "success": true,
 *   "data": { "id": "550e8400-e29b-41d4-a716-446655440000" }
 * }
 */
generationsRouter.delete(
  '/:id',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response) => {
    const service = getGenerationService();
    const { id } = req.params;

    const generation = await service.getById(id);
    if (!generation) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Generation not found',
          code: 'GENERATION_NOT_FOUND',
        },
      });
      return;
    }

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

    await service.delete(id);

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