/**
 * Validation & Error Handling Middleware
 *
 * Request validation using Zod schemas
 * Centralized error handling
 */

import { Request, Response, NextFunction } from 'express';
import { ZodError } from 'zod';
import { ValidationError, AppError } from '../types';
import { logger } from '../utils/logger';
import { ZodSchema } from 'zod';

/**
 * Validation middleware factory
 * Validates request body against Zod schema
 */
export function validateBody(schema: ZodSchema) {
  return (req: Request, _res: Response, next: NextFunction): void => {
    try {
      req.body = schema.parse(req.body);
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        const validationErrors = error.errors.map(err => ({
          field: err.path.join('.'),
          message: err.message,
          code: err.code,
        }));

        next(new ValidationError(
          'Validation failed',
          validationErrors
        ));
        return;
      }
      next(error);
    }
  };
}

/**
 * Validation middleware for query parameters
 */
export function validateQuery(schema: ZodSchema) {
  return (req: Request, _res: Response, next: NextFunction): void => {
    try {
      req.query = schema.parse(req.query);
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        const validationErrors = error.errors.map(err => ({
          field: err.path.join('.'),
          message: err.message,
          code: err.code,
        }));

        next(new ValidationError(
          'Query validation failed',
          validationErrors
        ));
        return;
      }
      next(error);
    }
  };
}

/**
 * Validation middleware for route parameters
 */
export function validateParams(schema: ZodSchema) {
  return (req: Request, _res: Response, next: NextFunction): void => {
    try {
      req.params = schema.parse(req.params);
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        const validationErrors = error.errors.map(err => ({
          field: err.path.join('.'),
          message: err.message,
          code: err.code,
        }));

        next(new ValidationError(
          'Parameter validation failed',
          validationErrors
        ));
        return;
      }
      next(error);
    }
  };
}

/**
 * Combine multiple validation middleware
 */
export function validate(validations: {
  body?: ZodSchema;
  query?: ZodSchema;
  params?: ZodSchema;
}) {
  const middlewares: Array<(req: Request, res: Response, next: NextFunction) => void> = [];

  if (validations.body) {
    middlewares.push(validateBody(validations.body));
  }

  if (validations.query) {
    middlewares.push(validateQuery(validations.query));
  }

  if (validations.params) {
    middlewares.push(validateParams(validations.params));
  }

  return middlewares;
}

/**
 * Global error handler middleware
 */
export function errorHandler(
  error: Error | AppError,
  req: Request,
  res: Response,
  _next: NextFunction
): void {
  // Log error
  logger.error({
    error: error.message,
    stack: error.stack,
    url: req.url,
    method: req.method,
    ip: req.ip,
    userId: req.user?.userId,
    correlationId: req.correlationId,
  }, 'Error occurred');

  // Handle known application errors
  if ('isOperational' in error && error.isOperational) {
    const appError = error as AppError;

    res.status(appError.statusCode).json({
      success: false,
      error: {
        code: appError.code,
        message: appError.message,
        details: appError.details,
      },
      meta: {
        timestamp: new Date().toISOString(),
        requestId: req.correlationId,
      },
    });
    return;
  }

  // Handle Zod validation errors
  if (error instanceof ZodError) {
    const validationErrors = error.errors.map(err => ({
      field: err.path.join('.'),
      message: err.message,
      code: err.code,
    }));

    res.status(400).json({
      success: false,
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Request validation failed',
        details: validationErrors,
      },
      meta: {
        timestamp: new Date().toISOString(),
        requestId: req.correlationId,
      },
    });
    return;
  }

  // Handle unknown errors
  res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production'
        ? 'An unexpected error occurred'
        : error.message,
    },
    meta: {
      timestamp: new Date().toISOString(),
      requestId: req.correlationId,
    },
  });
}

/**
 * Not found handler
 */
export function notFoundHandler(req: Request, res: Response): void {
  res.status(404).json({
    success: false,
    error: {
      code: 'NOT_FOUND',
      message: `Route ${req.method} ${req.url} not found`,
    },
    meta: {
      timestamp: new Date().toISOString(),
      requestId: req.correlationId,
    },
  });
}

/**
 * Async handler wrapper to catch async errors
 */
export function asyncHandler(
  fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
) {
  return (req: Request, res: Response, next: NextFunction): void => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

/**
 * Request logging middleware
 */
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
  const start = Date.now();

  // Log request
  logger.info({
    method: req.method,
    url: req.url,
    ip: req.ip,
    userAgent: req.get('user-agent'),
  }, 'Incoming request');

  // Log response
  res.on('finish', () => {
    const duration = Date.now() - start;
    const statusCode = res.statusCode;

    const logLevel = statusCode >= 500 ? 'error' :
                     statusCode >= 400 ? 'warn' : 'info';

    logger[logLevel]({
      method: req.method,
      url: req.url,
      statusCode,
      duration,
      ip: req.ip,
    }, 'Request completed');
  });

  next();
}

/**
 * Request ID middleware
 */
export function requestId(req: Request, res: Response, next: NextFunction): void {
  req.correlationId = req.headers['x-request-id'] as string ||
                      req.headers['x-correlation-id'] as string ||
                      `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;

  res.setHeader('X-Request-ID', req.correlationId);
  next();
}
