/**
 * Notification Controller
 * REST endpoints for notification management.
 *
 * @module Notifications
 */

import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import {
  NotificationService,
  SendNotificationDto,
  NotificationFilters,
} from '../services/notification.service';
import { TemplateService } from '../services/template.service';
import { ChannelService } from '../services/channel.service';
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';
import {
  Notification,
  NotificationStatus,
  NotificationPriority,
} from '../entities/notification.entity';
import { NotificationTemplate, TemplateTranslation } from '../entities/template.entity';
import { NotificationPreference } from '../entities/preference.entity';
import { Channel, ChannelType } from '../entities/channel.entity';
import { InAppNotification } from '../entities/in-app-notification.entity';

/**
 * Create notification controller router
 */
export function createNotificationController(dataSource: DataSource): Router {
  const router = Router();

  // Repositories
  const userRepository = dataSource.getRepository(User);
  const tenantRepository = dataSource.getRepository(Tenant);
  const refreshTokenRepository = dataSource.getRepository(RefreshToken);
  const notificationRepository = dataSource.getRepository(Notification);
  const templateRepository = dataSource.getRepository(NotificationTemplate);
  const translationRepository = dataSource.getRepository(TemplateTranslation);
  const preferenceRepository = dataSource.getRepository(NotificationPreference);
  const channelRepository = dataSource.getRepository(Channel);
  const inAppNotificationRepository = dataSource.getRepository(InAppNotification);

  // Services
  const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any);
  const authMiddleware = new AuthMiddleware(authService, dataSource);
  const notificationService = new NotificationService(
    notificationRepository,
    templateRepository,
    preferenceRepository,
    channelRepository,
    inAppNotificationRepository,
  );
  const templateService = new TemplateService(templateRepository, translationRepository);
  const channelService = new ChannelService(channelRepository);

  // Helper to get service context
  const getContext = (req: Request) => ({
    tenantId: req.tenantId!,
    userId: req.user?.sub,
  });

  /**
   * GET /notifications
   * List notifications with filters
   */
  router.get(
    '/',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const filters: NotificationFilters = {
          userId: req.query.userId as string,
          channelType: req.query.channelType as ChannelType,
          status: req.query.status as NotificationStatus,
          priority: req.query.priority as NotificationPriority,
          contextType: req.query.contextType as string,
          contextId: req.query.contextId as string,
          fromDate: req.query.fromDate ? new Date(req.query.fromDate as string) : undefined,
          toDate: req.query.toDate ? new Date(req.query.toDate as string) : undefined,
          page: parseInt(req.query.page as string) || 1,
          limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
        };

        const result = await notificationService.findAll(ctx, filters);

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

  /**
   * GET /notifications/stats
   * Get notification statistics
   */
  router.get(
    '/stats',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const options = {
          fromDate: req.query.fromDate ? new Date(req.query.fromDate as string) : undefined,
          toDate: req.query.toDate ? new Date(req.query.toDate as string) : undefined,
          userId: req.query.userId as string,
        };

        const stats = await notificationService.getStatistics(ctx, options);

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

  /**
   * GET /notifications/:id
   * Get notification by ID
   */
  router.get(
    '/:id',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const notification = await notificationService.findById(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

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

  /**
   * POST /notifications/send
   * Send a notification using a template
   */
  router.post(
    '/send',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const dto: SendNotificationDto = req.body;

        if (!dto.templateCode || !dto.channelType) {
          res.status(400).json({
            error: 'Bad Request',
            message: 'templateCode and channelType are required',
          });
          return;
        }

        const notification = await notificationService.send(ctx, dto);

        res.status(201).json({ success: true, data: notification });
      } catch (error) {
        if (error instanceof Error) {
          if (error.message.includes('Template not found')) {
            res.status(404).json({ error: 'Not Found', message: error.message });
            return;
          }
          if (error.message.includes('disabled')) {
            res.status(422).json({ error: 'Unprocessable Entity', message: error.message });
            return;
          }
        }
        next(error);
      }
    },
  );

  /**
   * POST /notifications/:id/mark-read
   * Mark notification as read
   */
  router.post(
    '/:id/mark-read',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const notification = await notificationService.markAsRead(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

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

  /**
   * POST /notifications/:id/mark-unread
   * Mark notification as unread
   */
  router.post(
    '/:id/mark-unread',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const notification = await notificationService.markAsUnread(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

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

  /**
   * POST /notifications/mark-read-bulk
   * Mark multiple notifications as read
   */
  router.post(
    '/mark-read-bulk',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const { ids } = req.body;
        if (!Array.isArray(ids) || ids.length === 0) {
          res.status(400).json({ error: 'Bad Request', message: 'ids array is required' });
          return;
        }

        const count = await notificationService.markMultipleAsRead(ctx, ids);

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

  /**
   * POST /notifications/:id/cancel
   * Cancel a pending notification
   */
  router.post(
    '/:id/cancel',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const notification = await notificationService.cancel(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

        res.status(200).json({ success: true, data: notification });
      } catch (error) {
        if (error instanceof Error && error.message.includes('Only pending')) {
          res.status(422).json({ error: 'Unprocessable Entity', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * POST /notifications/:id/retry
   * Retry a failed notification
   */
  router.post(
    '/:id/retry',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const notification = await notificationService.retry(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

        res.status(200).json({ success: true, data: notification });
      } catch (error) {
        if (error instanceof Error && error.message.includes('Only failed')) {
          res.status(422).json({ error: 'Unprocessable Entity', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  // ============= Template Routes =============

  /**
   * GET /notifications/templates
   * List notification templates
   */
  router.get(
    '/templates',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const filters = {
          category: req.query.category as any,
          channelType: req.query.channelType as ChannelType,
          isActive: req.query.isActive === 'true' ? true : req.query.isActive === 'false' ? false : undefined,
          isSystem: req.query.isSystem === 'true' ? true : req.query.isSystem === 'false' ? false : undefined,
          search: req.query.search as string,
          page: parseInt(req.query.page as string) || 1,
          limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
        };

        const result = await templateService.findAll(ctx, filters);

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

  /**
   * GET /notifications/templates/:id
   * Get template by ID
   */
  router.get(
    '/templates/:id',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const template = await templateService.findById(ctx, req.params.id);
        if (!template) {
          res.status(404).json({ error: 'Not Found', message: 'Template not found' });
          return;
        }

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

  /**
   * POST /notifications/templates
   * Create a new template
   */
  router.post(
    '/templates',
    authMiddleware.authenticate,
    authMiddleware.authorize('admin', 'super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const { code, name, channelType } = req.body;
        if (!code || !name || !channelType) {
          res.status(400).json({
            error: 'Bad Request',
            message: 'code, name, and channelType are required',
          });
          return;
        }

        const template = await templateService.create(ctx, req.body);

        res.status(201).json({ success: true, data: template });
      } catch (error) {
        if (error instanceof Error && error.message.includes('already exists')) {
          res.status(409).json({ error: 'Conflict', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * PATCH /notifications/templates/:id
   * Update a template
   */
  router.patch(
    '/templates/:id',
    authMiddleware.authenticate,
    authMiddleware.authorize('admin', 'super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const template = await templateService.update(ctx, req.params.id, req.body);
        if (!template) {
          res.status(404).json({ error: 'Not Found', message: 'Template not found' });
          return;
        }

        res.status(200).json({ success: true, data: template });
      } catch (error) {
        if (error instanceof Error && error.message.includes('Cannot modify system')) {
          res.status(403).json({ error: 'Forbidden', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * DELETE /notifications/templates/:id
   * Delete a template
   */
  router.delete(
    '/templates/:id',
    authMiddleware.authenticate,
    authMiddleware.authorize('admin', 'super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const deleted = await templateService.delete(ctx, req.params.id);
        if (!deleted) {
          res.status(404).json({ error: 'Not Found', message: 'Template not found' });
          return;
        }

        res.status(200).json({ success: true, message: 'Template deleted' });
      } catch (error) {
        if (error instanceof Error && error.message.includes('Cannot delete system')) {
          res.status(403).json({ error: 'Forbidden', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * POST /notifications/templates/:id/preview
   * Preview a template with sample variables
   */
  router.post(
    '/templates/:id/preview',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
          return;
        }

        const { variables, locale } = req.body;
        const rendered = await templateService.preview(ctx, req.params.id, variables, locale);

        res.status(200).json({ success: true, data: rendered });
      } catch (error) {
        if (error instanceof Error && error.message.includes('not found')) {
          res.status(404).json({ error: 'Not Found', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  // ============= Channel Routes =============

  /**
   * GET /notifications/channels
   * List notification channels
   */
  router.get(
    '/channels',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const filters = {
          channelType: req.query.channelType as ChannelType,
          isActive: req.query.isActive === 'true' ? true : req.query.isActive === 'false' ? false : undefined,
          provider: req.query.provider as string,
          page: parseInt(req.query.page as string) || 1,
          limit: Math.min(parseInt(req.query.limit as string) || 20, 100),
        };

        const result = await channelService.findAll(filters);

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

  /**
   * GET /notifications/channels/types
   * Get available channel types
   */
  router.get(
    '/channels/types',
    authMiddleware.authenticate,
    async (_req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const types = channelService.getChannelTypes();
        res.status(200).json({ success: true, data: types });
      } catch (error) {
        next(error);
      }
    },
  );

  /**
   * GET /notifications/channels/:id
   * Get channel by ID
   */
  router.get(
    '/channels/:id',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const channel = await channelService.findById(req.params.id);
        if (!channel) {
          res.status(404).json({ error: 'Not Found', message: 'Channel not found' });
          return;
        }

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

  /**
   * POST /notifications/channels
   * Create a new channel (admin only)
   */
  router.post(
    '/channels',
    authMiddleware.authenticate,
    authMiddleware.authorize('super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const { code, name, channelType } = req.body;
        if (!code || !name || !channelType) {
          res.status(400).json({
            error: 'Bad Request',
            message: 'code, name, and channelType are required',
          });
          return;
        }

        const channel = await channelService.create(req.body);

        res.status(201).json({ success: true, data: channel });
      } catch (error) {
        if (error instanceof Error && error.message.includes('already exists')) {
          res.status(409).json({ error: 'Conflict', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * PATCH /notifications/channels/:id
   * Update a channel (admin only)
   */
  router.patch(
    '/channels/:id',
    authMiddleware.authenticate,
    authMiddleware.authorize('super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const channel = await channelService.update(req.params.id, req.body);
        if (!channel) {
          res.status(404).json({ error: 'Not Found', message: 'Channel not found' });
          return;
        }

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

  /**
   * DELETE /notifications/channels/:id
   * Delete a channel (admin only)
   */
  router.delete(
    '/channels/:id',
    authMiddleware.authenticate,
    authMiddleware.authorize('super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const deleted = await channelService.delete(req.params.id);
        if (!deleted) {
          res.status(404).json({ error: 'Not Found', message: 'Channel not found' });
          return;
        }

        res.status(200).json({ success: true, message: 'Channel deleted' });
      } catch (error) {
        if (error instanceof Error && error.message.includes('Cannot delete')) {
          res.status(422).json({ error: 'Unprocessable Entity', message: error.message });
          return;
        }
        next(error);
      }
    },
  );

  /**
   * POST /notifications/channels/:id/test
   * Test channel connectivity
   */
  router.post(
    '/channels/:id/test',
    authMiddleware.authenticate,
    authMiddleware.authorize('admin', 'super_admin'),
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const result = await channelService.testChannel(req.params.id);

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

  return router;
}

export default createNotificationController;
