/**
 * NonConformityController - REST API for non-conformities (No Conformidades)
 *
 * Endpoints para gestion de no conformidades detectadas en inspecciones de calidad.
 *
 * @module Quality (MAI-009)
 * @routes /api/no-conformidades
 */

import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import {
  NonConformityService,
  NonConformityFilters,
  CreateNonConformityDto,
  UpdateNonConformityDto,
  CloseNonConformityDto,
} from '../services/non-conformity.service';
import {
  CorrectiveActionService,
  CreateCorrectiveActionDto,
} from '../services/corrective-action.service';
import { NonConformity, NCSeverity, NCStatus } from '../entities/non-conformity.entity';
import { CorrectiveAction } from '../entities/corrective-action.entity';
import { AuthMiddleware } from '../../auth/middleware/auth.middleware';
import { AuthService } from '../../auth/services/auth.service';
import { User } from '../../core/entities/user.entity';
import { Tenant } from '../../core/entities/tenant.entity';
import { RefreshToken } from '../../auth/entities/refresh-token.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

/**
 * Creates the NonConformity controller with all REST endpoints
 * @param dataSource - TypeORM DataSource for database operations
 * @returns Express Router with all endpoints configured
 */
export function createNonConformityController(dataSource: DataSource): Router {
  const router = Router();

  // Repositories
  const ncRepo = dataSource.getRepository(NonConformity);
  const actionRepo = dataSource.getRepository(CorrectiveAction);
  const userRepository = dataSource.getRepository(User);
  const tenantRepository = dataSource.getRepository(Tenant);
  const refreshTokenRepository = dataSource.getRepository(RefreshToken);

  // Services
  const service = new NonConformityService(ncRepo, actionRepo);
  const actionService = new CorrectiveActionService(actionRepo, ncRepo);
  const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any);
  const authMiddleware = new AuthMiddleware(authService, dataSource);

  /**
   * Helper to extract service context from request
   */
  const getContext = (req: Request): ServiceContext => {
    if (!req.tenantId) {
      throw new Error('Tenant ID is required');
    }
    return {
      tenantId: req.tenantId,
      userId: req.user?.sub,
    };
  };

  /**
   * GET /api/no-conformidades
   * List non-conformities with filters and pagination
   *
   * Query params:
   * - inspectionId: Filter by inspection
   * - loteId: Filter by lote
   * - contractorId: Filter by contractor
   * - severity: Filter by severity (minor, major, critical)
   * - status: Filter by status (open, in_progress, closed, verified)
   * - category: Filter by category (partial match)
   * - detectionDateFrom/To: Date range filters
   * - dueDateFrom/To: Due date range filters
   * - search: Search in ncNumber, description, category
   * - page: Page number (default: 1)
   * - limit: Items per page (default: 20, max: 100)
   */
  router.get('/', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const filters: NonConformityFilters = {};
      if (req.query.inspectionId) filters.inspectionId = req.query.inspectionId as string;
      if (req.query.loteId) filters.loteId = req.query.loteId as string;
      if (req.query.contractorId) filters.contractorId = req.query.contractorId as string;
      if (req.query.severity) filters.severity = req.query.severity as NCSeverity;
      if (req.query.status) filters.status = req.query.status as NCStatus;
      if (req.query.category) filters.category = req.query.category as string;
      if (req.query.detectionDateFrom) filters.detectionDateFrom = new Date(req.query.detectionDateFrom as string);
      if (req.query.detectionDateTo) filters.detectionDateTo = new Date(req.query.detectionDateTo as string);
      if (req.query.dueDateFrom) filters.dueDateFrom = new Date(req.query.dueDateFrom as string);
      if (req.query.dueDateTo) filters.dueDateTo = new Date(req.query.dueDateTo as string);
      if (req.query.search) filters.search = req.query.search as string;

      const page = parseInt(req.query.page as string) || 1;
      const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);

      const result = await service.findWithFilters(getContext(req), filters, page, limit);

      res.status(200).json({
        success: true,
        data: result.data,
        pagination: {
          total: result.total,
          page: result.page,
          limit: result.limit,
          totalPages: Math.ceil(result.total / result.limit),
        },
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/estadisticas
   * Get statistics for non-conformities
   */
  router.get('/estadisticas', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const stats = await service.getStatistics(getContext(req));

      res.status(200).json({
        success: true,
        data: stats,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/vencidas
   * Get overdue non-conformities
   */
  router.get('/vencidas', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const overdueNCs = await service.getOverdueNCs(getContext(req));

      res.status(200).json({
        success: true,
        data: overdueNCs,
        count: overdueNCs.length,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/por-severidad
   * Get open non-conformities count by severity
   */
  router.get('/por-severidad', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const bySeverity = await service.getOpenBySeverity(getContext(req));

      res.status(200).json({
        success: true,
        data: bySeverity,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/por-contratista/:contractorId
   * Get non-conformity statistics by contractor
   */
  router.get('/por-contratista/:contractorId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const stats = await service.getStatsByContractor(getContext(req), req.params.contractorId);

      res.status(200).json({
        success: true,
        data: stats,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/por-lote/:loteId
   * Get non-conformities for a specific lote
   */
  router.get('/por-lote/:loteId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const ncs = await service.findByLote(getContext(req), req.params.loteId);

      res.status(200).json({
        success: true,
        data: ncs,
        count: ncs.length,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/por-inspeccion/:inspectionId
   * Get non-conformities for a specific inspection
   */
  router.get('/por-inspeccion/:inspectionId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const ncs = await service.findByInspection(getContext(req), req.params.inspectionId);

      res.status(200).json({
        success: true,
        data: ncs,
        count: ncs.length,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/por-numero/:ncNumber
   * Get non-conformity by number
   */
  router.get('/por-numero/:ncNumber', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const nc = await service.findByNumber(getContext(req), req.params.ncNumber);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * GET /api/no-conformidades/:id
   * Get non-conformity detail with relations
   */
  router.get('/:id', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const nc = await service.findWithDetails(getContext(req), req.params.id);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades
   * Create new non-conformity
   *
   * Body:
   * - loteId: (required) UUID of the lote
   * - detectionDate: (required) Date of detection
   * - category: (required) Category string
   * - severity: (required) 'minor' | 'major' | 'critical'
   * - description: (required) Description text
   * - inspectionId: (optional) UUID of related inspection
   * - rootCause: (optional) Root cause analysis
   * - photoUrl: (optional) Evidence photo URL
   * - contractorId: (optional) UUID of responsible contractor
   * - dueDate: (optional) Due date (auto-calculated based on severity if not provided)
   */
  router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality', 'resident'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      // Validate required fields
      const { loteId, detectionDate, category, severity, description } = req.body;
      if (!loteId || !detectionDate || !category || !severity || !description) {
        res.status(400).json({
          error: 'Bad Request',
          message: 'loteId, detectionDate, category, severity y description son requeridos',
        });
        return;
      }

      // Validate severity enum
      const validSeverities: NCSeverity[] = ['minor', 'major', 'critical'];
      if (!validSeverities.includes(severity)) {
        res.status(400).json({
          error: 'Bad Request',
          message: `Severidad invalida. Valores validos: ${validSeverities.join(', ')}`,
        });
        return;
      }

      const dto: CreateNonConformityDto = {
        inspectionId: req.body.inspectionId,
        loteId,
        detectionDate: new Date(detectionDate),
        category,
        severity,
        description,
        rootCause: req.body.rootCause,
        photoUrl: req.body.photoUrl,
        contractorId: req.body.contractorId,
        dueDate: req.body.dueDate ? new Date(req.body.dueDate) : undefined,
      };

      const nc = await service.create(getContext(req), dto);

      res.status(201).json({
        success: true,
        data: nc,
        message: 'No conformidad registrada exitosamente',
      });
    } catch (error) {
      next(error);
    }
  });

  /**
   * PUT /api/no-conformidades/:id
   * Update non-conformity
   */
  router.put('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality', 'resident'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const dto: UpdateNonConformityDto = {};
      if (req.body.loteId !== undefined) dto.loteId = req.body.loteId;
      if (req.body.category !== undefined) dto.category = req.body.category;
      if (req.body.severity !== undefined) dto.severity = req.body.severity;
      if (req.body.description !== undefined) dto.description = req.body.description;
      if (req.body.rootCause !== undefined) dto.rootCause = req.body.rootCause;
      if (req.body.photoUrl !== undefined) dto.photoUrl = req.body.photoUrl;
      if (req.body.contractorId !== undefined) dto.contractorId = req.body.contractorId;
      if (req.body.dueDate !== undefined) dto.dueDate = new Date(req.body.dueDate);

      const nc = await service.update(getContext(req), req.params.id, dto);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'No conformidad actualizada exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Cannot update')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/accion-correctiva
   * Add corrective action to non-conformity
   *
   * Body:
   * - actionType: (required) 'corrective' | 'preventive' | 'improvement'
   * - description: (required) Action description
   * - responsibleId: (required) UUID of responsible user
   * - dueDate: (required) Due date for the action
   */
  router.post('/:id/accion-correctiva', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality', 'resident'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const { actionType, description, responsibleId, dueDate } = req.body;
      if (!actionType || !description || !responsibleId || !dueDate) {
        res.status(400).json({
          error: 'Bad Request',
          message: 'actionType, description, responsibleId y dueDate son requeridos',
        });
        return;
      }

      const dto: CreateCorrectiveActionDto = {
        nonConformityId: req.params.id,
        actionType,
        description,
        responsibleId,
        dueDate: new Date(dueDate),
      };

      const action = await actionService.create(getContext(req), dto);

      res.status(201).json({
        success: true,
        data: action,
        message: 'Accion correctiva agregada exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('not found')) {
        res.status(404).json({ error: 'Not Found', message: error.message });
        return;
      }
      if (error.message?.includes('Cannot add')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/asignar-contratista
   * Assign contractor to non-conformity
   */
  router.post('/:id/asignar-contratista', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const { contractorId } = req.body;
      if (!contractorId) {
        res.status(400).json({ error: 'Bad Request', message: 'contractorId es requerido' });
        return;
      }

      const nc = await service.assignContractor(getContext(req), req.params.id, contractorId);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'Contratista asignado exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Cannot assign')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/causa-raiz
   * Set root cause analysis
   */
  router.post('/:id/causa-raiz', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality', 'resident'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const { rootCause } = req.body;
      if (!rootCause) {
        res.status(400).json({ error: 'Bad Request', message: 'rootCause es requerido' });
        return;
      }

      const nc = await service.setRootCause(getContext(req), req.params.id, rootCause);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'Causa raiz registrada exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Cannot modify')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/cerrar
   * Close non-conformity
   *
   * Body:
   * - closurePhotoUrl: (optional) URL of closure evidence photo
   * - closureNotes: (optional) Closure notes
   */
  router.post('/:id/cerrar', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const dto: CloseNonConformityDto = {
        closurePhotoUrl: req.body.closurePhotoUrl,
        closureNotes: req.body.closureNotes,
      };

      const nc = await service.close(getContext(req), req.params.id, dto);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'No conformidad cerrada exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Can only close') || error.message?.includes('Cannot close')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/verificar
   * Verify closed non-conformity
   *
   * Body:
   * - verificationNotes: (optional) Verification notes
   */
  router.post('/:id/verificar', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const dto = {
        verificationNotes: req.body.verificationNotes,
      };

      const nc = await service.verify(getContext(req), req.params.id, dto);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'No conformidad verificada exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Can only verify') || error.message?.includes('Cannot verify')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  /**
   * POST /api/no-conformidades/:id/reabrir
   * Reopen closed or verified non-conformity
   *
   * Body:
   * - reason: (required) Reason for reopening
   */
  router.post('/:id/reabrir', authMiddleware.authenticate, authMiddleware.authorize('admin', 'quality'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const tenantId = req.tenantId;
      if (!tenantId) {
        res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
        return;
      }

      const { reason } = req.body;
      if (!reason) {
        res.status(400).json({ error: 'Bad Request', message: 'reason es requerido' });
        return;
      }

      const nc = await service.reopen(getContext(req), req.params.id, reason);
      if (!nc) {
        res.status(404).json({ error: 'Not Found', message: 'No conformidad no encontrada' });
        return;
      }

      res.status(200).json({
        success: true,
        data: nc,
        message: 'No conformidad reabierta exitosamente',
      });
    } catch (error: any) {
      if (error.message?.includes('Can only reopen')) {
        res.status(400).json({ error: 'Bad Request', message: error.message });
        return;
      }
      next(error);
    }
  });

  return router;
}
