import { Response, Router } from 'express';
import type { Router as RouterType } from 'express';
import { FlowService, 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 { validateAndNormalizePagination } from '@/utils/validators';
import { buildPaginatedResponse } from '@/utils/helpers';
import { toFlowResponse, toGenerationResponse, toImageResponse } from '@/types/responses';
import type {
  ListFlowsResponse,
  GetFlowResponse,
  UpdateFlowAliasesResponse,
  ListFlowGenerationsResponse,
  ListFlowImagesResponse,
} from '@/types/responses';

export const flowsRouter: RouterType = Router();

let flowService: FlowService;
let generationService: GenerationService;

const getFlowService = (): FlowService => {
  if (!flowService) {
    flowService = new FlowService();
  }
  return flowService;
};

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

/**
 * POST /api/v1/flows
 * REMOVED (Section 4.3): Lazy flow creation pattern
 * Flows are now created automatically when:
 * - A generation/upload specifies a flowId
 * - A generation/upload provides a flowAlias (eager creation)
 *
 * @deprecated Flows are created automatically, no explicit endpoint needed
 */
// flowsRouter.post(
//   '/',
//   validateApiKey,
//   requireProjectKey,
//   asyncHandler(async (req: any, res: Response<CreateFlowResponse>) => {
//     const service = getFlowService();
//     const { meta } = req.body;
//
//     const projectId = req.apiKey.projectId;
//
//     const flow = await service.create({
//       projectId,
//       aliases: {},
//       meta: meta || {},
//     });
//
//     res.status(201).json({
//       success: true,
//       data: toFlowResponse(flow),
//     });
//   })
// );

/**
 * List all flows for a project with pagination and computed counts
 *
 * Retrieves flows created automatically when generations/uploads specify:
 * - A flowId in their request
 * - A flowAlias (creates flow eagerly if doesn't exist)
 *
 * Each flow includes:
 * - Computed generationCount and imageCount
 * - Flow-scoped aliases (JSONB key-value pairs)
 * - Custom metadata
 *
 * @route GET /api/v1/flows
 * @authentication Project Key required
 *
 * @param {number} [req.query.limit=20] - Results per page (max 100)
 * @param {number} [req.query.offset=0] - Number of results to skip
 *
 * @returns {ListFlowsResponse} 200 - Paginated list of flows with counts
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * GET /api/v1/flows?limit=50&offset=0
 */
flowsRouter.get(
  '/',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListFlowsResponse>) => {
    const service = getFlowService();
    const { limit, offset } = 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 },
      validatedLimit,
      validatedOffset
    );

    const responseData = result.flows.map((flow) => toFlowResponse(flow));

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

/**
 * Get a single flow by ID with computed statistics
 *
 * Retrieves detailed flow information including:
 * - All flow-scoped aliases
 * - Computed generationCount (active generations only)
 * - Computed imageCount (active images only)
 * - Custom metadata
 * - Creation and update timestamps
 *
 * @route GET /api/v1/flows/:id
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 *
 * @returns {GetFlowResponse} 200 - Complete flow details with counts
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} FLOW_NOT_FOUND - Flow does not exist
 *
 * @example
 * GET /api/v1/flows/550e8400-e29b-41d4-a716-446655440000
 */
flowsRouter.get(
  '/:id',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<GetFlowResponse>) => {
    const service = getFlowService();
    const { id } = req.params;

    const flow = await service.getByIdWithCounts(id);

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

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

/**
 * List all generations in a specific flow with pagination
 *
 * Retrieves all generations associated with this flow, ordered by creation date (newest first).
 * Includes only active (non-deleted) generations.
 *
 * @route GET /api/v1/flows/:id/generations
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 * @param {number} [req.query.limit=20] - Results per page (max 100)
 * @param {number} [req.query.offset=0] - Number of results to skip
 *
 * @returns {ListFlowGenerationsResponse} 200 - Paginated list of generations
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * GET /api/v1/flows/550e8400-e29b-41d4-a716-446655440000/generations?limit=10
 */
flowsRouter.get(
  '/:id/generations',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListFlowGenerationsResponse>) => {
    const service = getFlowService();
    const { id } = req.params;
    const { limit, offset } = req.query;

    const flow = await service.getById(id);
    if (!flow) {
      res.status(404).json({
        success: false,
        data: [],
        pagination: { total: 0, limit: 20, offset: 0, hasMore: false },
      });
      return;
    }

    if (flow.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        data: [],
        pagination: { total: 0, limit: 20, offset: 0, hasMore: false },
      });
      return;
    }

    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 result = await service.getFlowGenerations(id, validatedLimit, validatedOffset);

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

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

/**
 * List all images in a specific flow with pagination
 *
 * Retrieves all images (generated and uploaded) associated with this flow,
 * ordered by creation date (newest first). Includes only active (non-deleted) images.
 *
 * @route GET /api/v1/flows/:id/images
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 * @param {number} [req.query.limit=20] - Results per page (max 100)
 * @param {number} [req.query.offset=0] - Number of results to skip
 *
 * @returns {ListFlowImagesResponse} 200 - Paginated list of images
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 400 - Invalid pagination parameters
 * @returns {object} 401 - Missing or invalid API key
 *
 * @example
 * GET /api/v1/flows/550e8400-e29b-41d4-a716-446655440000/images?limit=20
 */
flowsRouter.get(
  '/:id/images',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<ListFlowImagesResponse>) => {
    const service = getFlowService();
    const { id } = req.params;
    const { limit, offset } = req.query;

    const flow = await service.getById(id);
    if (!flow) {
      res.status(404).json({
        success: false,
        data: [],
        pagination: { total: 0, limit: 20, offset: 0, hasMore: false },
      });
      return;
    }

    if (flow.projectId !== req.apiKey.projectId) {
      res.status(404).json({
        success: false,
        data: [],
        pagination: { total: 0, limit: 20, offset: 0, hasMore: false },
      });
      return;
    }

    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 result = await service.getFlowImages(id, validatedLimit, validatedOffset);

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

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

/**
 * Update flow-scoped aliases (add or modify existing)
 *
 * Updates the JSONB aliases field with new or modified key-value pairs.
 * Aliases are merged with existing aliases (does not replace all).
 *
 * Flow-scoped aliases:
 * - Must start with @ symbol
 * - Unique within the flow only (not project-wide)
 * - Used for alias resolution in generations
 * - Stored as JSONB for efficient lookups
 *
 * @route PUT /api/v1/flows/:id/aliases
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 * @param {UpdateFlowAliasesRequest} req.body - Alias updates
 * @param {object} req.body.aliases - Key-value pairs of aliases to add/update
 *
 * @returns {UpdateFlowAliasesResponse} 200 - Updated flow with merged aliases
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 400 - Invalid aliases format
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} FLOW_NOT_FOUND - Flow does not exist
 * @throws {Error} VALIDATION_ERROR - Aliases must be an object
 *
 * @example
 * PUT /api/v1/flows/550e8400-e29b-41d4-a716-446655440000/aliases
 * {
 *   "aliases": {
 *     "@hero": "image-id-123",
 *     "@background": "image-id-456"
 *   }
 * }
 */
flowsRouter.put(
  '/:id/aliases',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response<UpdateFlowAliasesResponse>) => {
    const service = getFlowService();
    const { id } = req.params;
    const { aliases } = req.body;

    if (!aliases || typeof aliases !== 'object' || Array.isArray(aliases)) {
      res.status(400).json({
        success: false,
        error: {
          message: 'Aliases must be an object with key-value pairs',
          code: 'VALIDATION_ERROR',
        },
      });
      return;
    }

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

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

    const updatedFlow = await service.updateAliases(id, aliases);

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

/**
 * Remove a specific alias from a flow
 *
 * Deletes a single alias key-value pair from the flow's JSONB aliases field.
 * Other aliases remain unchanged.
 *
 * @route DELETE /api/v1/flows/:id/aliases/:alias
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 * @param {string} req.params.alias - Alias to remove (e.g., "@hero")
 *
 * @returns {object} 200 - Updated flow with alias removed
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} FLOW_NOT_FOUND - Flow does not exist
 *
 * @example
 * DELETE /api/v1/flows/550e8400-e29b-41d4-a716-446655440000/aliases/@hero
 */
flowsRouter.delete(
  '/:id/aliases/:alias',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response) => {
    const service = getFlowService();
    const { id, alias } = req.params;

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

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

    const updatedFlow = await service.removeAlias(id, alias);

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

/**
 * Regenerate the most recent generation in a flow (Section 3.6)
 *
 * Logic:
 * 1. Find the flow by ID
 * 2. Query for the most recent generation (ordered by createdAt desc)
 * 3. Trigger regeneration with exact same parameters
 * 4. Replace existing output image (preserves ID and URLs)
 *
 * @route POST /api/v1/flows/:id/regenerate
 * @authentication Project Key required
 * @rateLimit 100 requests per hour per API key
 *
 * @param {string} req.params.id - Flow ID (affects: determines which flow's latest generation to regenerate)
 *
 * @returns {object} 200 - Regenerated generation with updated output image
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 400 - Flow has no generations
 * @returns {object} 401 - Missing or invalid API key
 * @returns {object} 429 - Rate limit exceeded
 *
 * @throws {Error} FLOW_NOT_FOUND - Flow does not exist
 * @throws {Error} FLOW_HAS_NO_GENERATIONS - Flow contains no generations to regenerate
 *
 * @example
 * POST /api/v1/flows/550e8400-e29b-41d4-a716-446655440000/regenerate
 */
flowsRouter.post(
  '/:id/regenerate',
  validateApiKey,
  requireProjectKey,
  rateLimitByApiKey,
  asyncHandler(async (req: any, res: Response) => {
    const flowSvc = getFlowService();
    const genSvc = getGenerationService();
    const { id } = req.params;

    const flow = await flowSvc.getById(id);
    if (!flow) {
      res.status(404).json({
        success: false,
        error: {
          message: 'Flow not found',
          code: 'FLOW_NOT_FOUND',
        },
      });
      return;
    }

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

    // Get the most recent generation in the flow
    const result = await flowSvc.getFlowGenerations(id, 1, 0); // limit=1, offset=0

    if (result.total === 0 || result.generations.length === 0) {
      res.status(400).json({
        success: false,
        error: {
          message: 'Flow has no generations to regenerate',
          code: 'FLOW_HAS_NO_GENERATIONS',
        },
      });
      return;
    }

    const latestGeneration = result.generations[0]!;

    // Regenerate the latest generation
    const regenerated = await genSvc.regenerate(latestGeneration.id);

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

/**
 * Delete a flow with cascade deletion (Section 7.3)
 *
 * Permanently removes the flow with cascade behavior:
 * - Flow record is hard deleted
 * - All generations in flow are hard deleted
 * - Images WITHOUT project alias: hard deleted with MinIO cleanup
 * - Images WITH project alias: kept, but flowId set to NULL (unlinked)
 *
 * Rationale: Images with project aliases are used globally and should be preserved.
 * Flow deletion removes the organizational structure but protects important assets.
 *
 * @route DELETE /api/v1/flows/:id
 * @authentication Project Key required
 *
 * @param {string} req.params.id - Flow ID (UUID)
 *
 * @returns {object} 200 - Deletion confirmation with flow ID
 * @returns {object} 404 - Flow not found or access denied
 * @returns {object} 401 - Missing or invalid API key
 *
 * @throws {Error} FLOW_NOT_FOUND - Flow does not exist
 *
 * @example
 * DELETE /api/v1/flows/550e8400-e29b-41d4-a716-446655440000
 *
 * Response:
 * {
 *   "success": true,
 *   "data": { "id": "550e8400-e29b-41d4-a716-446655440000" }
 * }
 */
flowsRouter.delete(
  '/:id',
  validateApiKey,
  requireProjectKey,
  asyncHandler(async (req: any, res: Response) => {
    const service = getFlowService();
    const { id } = req.params;

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

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

    await service.delete(id);

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