import { Response, Router } from 'express';
import type { Router as RouterType } from 'express';
import { LiveScopeService, ImageService, 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 { PAGINATION_LIMITS, ERROR_MESSAGES } from '@/utils/constants';
import { buildPaginationMeta } from '@/utils/helpers';
import { toLiveScopeResponse, toImageResponse } from '@/types/responses';
import type {
  CreateLiveScopeRequest,
  ListLiveScopesQuery,
  UpdateLiveScopeRequest,
  RegenerateScopeRequest,
} from '@/types/requests';
import type {
  CreateLiveScopeResponse,
  GetLiveScopeResponse,
  ListLiveScopesResponse,
  UpdateLiveScopeResponse,
  DeleteLiveScopeResponse,
  RegenerateScopeResponse,
} from '@/types/responses';

export const scopesRouter: RouterType = Router();

let scopeService: LiveScopeService;
let imageService: ImageService;
let generationService: GenerationService;

const getScopeService = (): LiveScopeService => {
  if (!scopeService) {
    scopeService = new LiveScopeService();
  }
  return scopeService;
};

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

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

/**
 * Create a new live scope manually with settings
 *
 * Creates a live scope for organizing live URL generations:
 * - Slug must be unique within the project
 * - Slug format: alphanumeric + hyphens + underscores only
 * - Configure generation limits and permissions
 * - Optional custom metadata storage
 *
 * Note: Scopes are typically auto-created via live URLs, but this endpoint
 * allows pre-configuration with specific settings.
 *
 * @route POST /api/v1/live/scopes
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {CreateLiveScopeRequest} req.body - Scope configuration
 * @param {string} req.body.slug - Unique scope identifier (alphanumeric + hyphens + underscores)
 * @param {boolean} [req.body.allowNewGenerations=true] - Allow new generations in scope
 * @param {number} [req.body.newGenerationsLimit=30] - Maximum generations allowed
 * @param {object} [req.body.meta] - Custom metadata
 *
 * @returns {CreateLiveScopeResponse} 201 - Created scope with stats
 * @returns {object} 400 - Invalid slug format
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 409 - Scope slug already exists
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} SCOPE_INVALID_FORMAT - Invalid slug format
 * @throws {Error} SCOPE_ALREADY_EXISTS - Slug already in use
 *
 * @example
 * POST /api/v1/live/scopes
 * {
 *   "slug": "hero-section",
 *   "allowNewGenerations": true,
 *   "newGenerationsLimit": 50,
 *   "meta": { "description": "Hero section images" }
 * }
 */
scopesRouter.post(
  '/',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<CreateLiveScopeResponse>) => {
    const service = getScopeService();
    const { slug, allowNewGenerations, newGenerationsLimit, meta } = req.body as CreateLiveScopeRequest;
    const projectId = req.apiKey.projectId;

    // Validate slug format
    if (!slug || !/^[a-zA-Z0-9_-]+$/.test(slug)) {
      res.status(400).json({
        success: false,
        error: {
          message: ERROR_MESSAGES.SCOPE_INVALID_FORMAT,
          code: 'SCOPE_INVALID_FORMAT',
        },
      });
      return;
    }

    // Check if scope already exists
    const existing = await service.getBySlug(projectId, slug);
    if (existing) {
      res.status(409).json({
        success: false,
        error: {
          message: 'Scope with this slug already exists',
          code: 'SCOPE_ALREADY_EXISTS',
        },
      });
      return;
    }

    // Create scope
    const scope = await service.create({
      projectId,
      slug,
      allowNewGenerations: allowNewGenerations ?? true,
      newGenerationsLimit: newGenerationsLimit ?? 30,
      meta: meta || {},
    });

    // Get with stats
    const scopeWithStats = await service.getByIdWithStats(scope.id);

    res.status(201).json({
      success: true,
      data: toLiveScopeResponse(scopeWithStats),
    });
  }),
);

/**
 * List all live scopes for the project with pagination and statistics
 *
 * Retrieves all scopes (both auto-created and manually created) with:
 * - Computed currentGenerations count (active only)
 * - Last generation timestamp
 * - Pagination support
 * - Optional slug filtering
 *
 * @route GET /api/v1/live/scopes
 * @authentication Project Key required
 *
 * @param {string} [req.query.slug] - Filter by exact slug match
 * @param {number} [req.query.limit=20] - Results per page (max 100)
 * @param {number} [req.query.offset=0] - Number of results to skip
 *
 * @returns {ListLiveScopesResponse} 200 - Paginated list of scopes with stats
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * GET /api/v1/live/scopes?limit=50&offset=0
 */
scopesRouter.get(
  '/',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListLiveScopesResponse>) => {
    const service = getScopeService();
    const { slug, limit, offset } = req.query as ListLiveScopesQuery;
    const projectId = req.apiKey.projectId;

    const parsedLimit = Math.min(
      (limit ? parseInt(limit.toString(), 10) : PAGINATION_LIMITS.DEFAULT_LIMIT) || PAGINATION_LIMITS.DEFAULT_LIMIT,
      PAGINATION_LIMITS.MAX_LIMIT,
    );
    const parsedOffset = (offset ? parseInt(offset.toString(), 10) : 0) || 0;

    const result = await service.list(
      { projectId, slug },
      parsedLimit,
      parsedOffset,
    );

    const scopeResponses = result.scopes.map(toLiveScopeResponse);

    res.json({
      success: true,
      data: scopeResponses,
      pagination: buildPaginationMeta(result.total, parsedLimit, parsedOffset),
    });
  }),
);

/**
 * Get a single live scope by slug with complete statistics
 *
 * Retrieves detailed scope information including:
 * - Current generation count (active generations only)
 * - Last generation timestamp
 * - Settings (allowNewGenerations, newGenerationsLimit)
 * - Custom metadata
 * - Creation and update timestamps
 *
 * @route GET /api/v1/live/scopes/:slug
 * @authentication Project Key required
 *
 * @param {string} req.params.slug - Scope slug identifier
 *
 * @returns {GetLiveScopeResponse} 200 - Complete scope details with stats
 * @returns {object} 404 - Scope not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} SCOPE_NOT_FOUND - Scope does not exist
 *
 * @example
 * GET /api/v1/live/scopes/hero-section
 */
scopesRouter.get(
  '/:slug',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<GetLiveScopeResponse>) => {
    const service = getScopeService();
    const { slug } = req.params;
    const projectId = req.apiKey.projectId;

    const scopeWithStats = await service.getBySlugWithStats(projectId, slug);

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

/**
 * Update live scope settings and metadata
 *
 * Modifies scope configuration:
 * - Enable/disable new generations
 * - Adjust generation limits
 * - Update custom metadata
 *
 * Changes take effect immediately for new live URL requests.
 *
 * @route PUT /api/v1/live/scopes/:slug
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.slug - Scope slug identifier
 * @param {UpdateLiveScopeRequest} req.body - Update parameters
 * @param {boolean} [req.body.allowNewGenerations] - Allow/disallow new generations
 * @param {number} [req.body.newGenerationsLimit] - Update generation limit
 * @param {object} [req.body.meta] - Update custom metadata
 *
 * @returns {UpdateLiveScopeResponse} 200 - Updated scope with stats
 * @returns {object} 404 - Scope not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} SCOPE_NOT_FOUND - Scope does not exist
 *
 * @example
 * PUT /api/v1/live/scopes/hero-section
 * {
 *   "allowNewGenerations": false,
 *   "newGenerationsLimit": 100
 * }
 */
scopesRouter.put(
  '/:slug',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<UpdateLiveScopeResponse>) => {
    const service = getScopeService();
    const { slug } = req.params;
    const { allowNewGenerations, newGenerationsLimit, meta } = req.body as UpdateLiveScopeRequest;
    const projectId = req.apiKey.projectId;

    // Get scope
    const scope = await service.getBySlugOrThrow(projectId, slug);

    // Update scope
    const updates: {
      allowNewGenerations?: boolean;
      newGenerationsLimit?: number;
      meta?: Record<string, unknown>;
    } = {};
    if (allowNewGenerations !== undefined) updates.allowNewGenerations = allowNewGenerations;
    if (newGenerationsLimit !== undefined) updates.newGenerationsLimit = newGenerationsLimit;
    if (meta !== undefined) updates.meta = meta;

    await service.update(scope.id, updates);

    // Get updated scope with stats
    const updated = await service.getByIdWithStats(scope.id);

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

/**
 * Regenerate images in a live scope
 *
 * Regenerates either a specific image or all images in the scope:
 * - Specific image: Provide imageId in request body
 * - All images: Omit imageId to regenerate entire scope
 * - Uses exact same parameters (prompt, aspect ratio, etc.)
 * - Updates existing images (preserves IDs and URLs)
 * - Verifies image belongs to scope before regenerating
 *
 * Useful for refreshing stale cached images or recovering from failures.
 *
 * @route POST /api/v1/live/scopes/:slug/regenerate
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.slug - Scope slug identifier
 * @param {RegenerateScopeRequest} [req.body] - Regeneration options
 * @param {string} [req.body.imageId] - Specific image to regenerate (omit for all)
 *
 * @returns {RegenerateScopeResponse} 200 - Regeneration results
 * @returns {object} 400 - Image not in scope
 * @returns {object} 404 - Scope or image not found
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} SCOPE_NOT_FOUND - Scope does not exist
 * @throws {Error} IMAGE_NOT_FOUND - Image does not exist
 * @throws {Error} IMAGE_NOT_IN_SCOPE - Image doesn't belong to scope
 *
 * @example
 * // Regenerate specific image
 * POST /api/v1/live/scopes/hero-section/regenerate
 * {
 *   "imageId": "550e8400-e29b-41d4-a716-446655440000"
 * }
 *
 * @example
 * // Regenerate all images in scope
 * POST /api/v1/live/scopes/hero-section/regenerate
 * {}
 */
scopesRouter.post(
  '/:slug/regenerate',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<RegenerateScopeResponse>) => {
    const scopeService = getScopeService();
    const imgService = getImageService();
    const genService = getGenerationService();
    const { slug } = req.params;
    const { imageId } = req.body as RegenerateScopeRequest;
    const projectId = req.apiKey.projectId;

    // Get scope
    const scope = await scopeService.getBySlugWithStats(projectId, slug);

    if (imageId) {
      // Regenerate specific image
      const image = await imgService.getById(imageId);
      if (!image) {
        res.status(404).json({
          success: false,
          error: {
            message: ERROR_MESSAGES.IMAGE_NOT_FOUND,
            code: 'IMAGE_NOT_FOUND',
          },
        });
        return;
      }

      // Check if image belongs to this scope
      const imageMeta = image.meta as Record<string, unknown>;
      if (imageMeta['scope'] !== slug) {
        res.status(400).json({
          success: false,
          error: {
            message: 'Image does not belong to this scope',
            code: 'IMAGE_NOT_IN_SCOPE',
          },
        });
        return;
      }

      // Regenerate the image's generation
      if (image.generationId) {
        await genService.regenerate(image.generationId);
      }

      const regeneratedImage = await imgService.getById(imageId);

      res.json({
        success: true,
        data: {
          regenerated: 1,
          images: regeneratedImage ? [toImageResponse(regeneratedImage)] : [],
        },
      });
    } else {
      // Regenerate all images in scope
      if (!scope.images || scope.images.length === 0) {
        res.json({
          success: true,
          data: {
            regenerated: 0,
            images: [],
          },
        });
        return;
      }

      const regeneratedImages = [];
      for (const image of scope.images) {
        if (image.generationId) {
          await genService.regenerate(image.generationId);
          const regenerated = await imgService.getById(image.id);
          if (regenerated) {
            regeneratedImages.push(toImageResponse(regenerated));
          }
        }
      }

      res.json({
        success: true,
        data: {
          regenerated: regeneratedImages.length,
          images: regeneratedImages,
        },
      });
    }
  }),
);

/**
 * Delete a live scope with cascading image deletion
 *
 * Permanently removes the scope and all its associated images:
 * - Hard deletes all images in scope (MinIO + database)
 * - Follows alias protection rules for each image
 * - Hard deletes scope record (no soft delete)
 * - Cannot be undone
 *
 * Use with caution: This is a destructive operation that permanently
 * removes the scope and all cached live URL images.
 *
 * @route DELETE /api/v1/live/scopes/:slug
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.slug - Scope slug identifier
 *
 * @returns {DeleteLiveScopeResponse} 200 - Deletion confirmation with scope ID
 * @returns {object} 404 - Scope not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} SCOPE_NOT_FOUND - Scope does not exist
 *
 * @example
 * DELETE /api/v1/live/scopes/hero-section
 *
 * Response:
 * {
 *   "success": true,
 *   "data": { "id": "550e8400-e29b-41d4-a716-446655440000" }
 * }
 */
scopesRouter.delete(
  '/:slug',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response<DeleteLiveScopeResponse>) => {
    const scopeService = getScopeService();
    const imgService = getImageService();
    const { slug } = req.params;
    const projectId = req.apiKey.projectId;

    // Get scope with images
    const scope = await scopeService.getBySlugWithStats(projectId, slug);

    // Delete all images in scope (follows alias protection rules)
    if (scope.images) {
      for (const image of scope.images) {
        await imgService.hardDelete(image.id);
      }
    }

    // Delete scope record
    await scopeService.delete(scope.id);

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