/**
 * In-App Notification Controller
 * REST endpoints for in-app notification management.
 *
 * @module Notifications
 */

import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import {
  InAppNotificationService,
  CreateInAppNotificationDto,
  InAppNotificationFilters,
} from '../services/in-app-notification.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 { InAppNotification, InAppCategory, InAppPriority } from '../entities/in-app-notification.entity';

/**
 * Create in-app notification controller router
 */
export function createInAppNotificationController(dataSource: DataSource): Router {
  const router = Router();

  // Repositories
  const userRepository = dataSource.getRepository(User);
  const tenantRepository = dataSource.getRepository(Tenant);
  const refreshTokenRepository = dataSource.getRepository(RefreshToken);
  const inAppNotificationRepository = dataSource.getRepository(InAppNotification);

  // Services
  const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any);
  const authMiddleware = new AuthMiddleware(authService, dataSource);
  const inAppNotificationService = new InAppNotificationService(inAppNotificationRepository);

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

  /**
   * GET /notifications/in-app
   * Get all in-app notifications for current user
   */
  router.get(
    '/',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const includeRead = req.query.includeRead === 'true';
        const includeArchived = req.query.includeArchived === 'true';
        const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);

        const notifications = await inAppNotificationService.findByUser(ctx, ctx.userId, {
          includeRead,
          includeArchived,
          limit,
        });

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

  /**
   * GET /notifications/in-app/counts
   * Get notification counts for current user
   */
  router.get(
    '/counts',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const counts = await inAppNotificationService.getCounts(ctx, ctx.userId);

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

  /**
   * GET /notifications/in-app/recent
   * Get recent notifications since a given date
   */
  router.get(
    '/recent',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const since = req.query.since
          ? new Date(req.query.since as string)
          : new Date(Date.now() - 24 * 60 * 60 * 1000); // Default: last 24 hours

        const notifications = await inAppNotificationService.getRecent(ctx, ctx.userId, since);

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

  /**
   * GET /notifications/in-app/:id
   * Get a specific in-app notification
   */
  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 inAppNotificationService.findById(ctx, req.params.id);
        if (!notification) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

        // Ensure user can only access their own notifications
        if (notification.userId !== ctx.userId) {
          res.status(403).json({ error: 'Forbidden', message: 'Access denied' });
          return;
        }

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

  /**
   * POST /notifications/in-app/:id/read
   * Mark notification as read
   */
  router.post(
    '/:id/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 inAppNotificationService.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/in-app/:id/unread
   * Mark notification as unread
   */
  router.post(
    '/:id/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 inAppNotificationService.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/in-app/read-all
   * Mark all notifications as read for current user
   */
  router.post(
    '/read-all',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const count = await inAppNotificationService.markAllAsRead(ctx, ctx.userId);

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

  /**
   * POST /notifications/in-app/read-bulk
   * Mark multiple notifications as read
   */
  router.post(
    '/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 inAppNotificationService.markMultipleAsRead(ctx, ids);

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

  /**
   * POST /notifications/in-app/:id/archive
   * Archive a notification
   */
  router.post(
    '/:id/archive',
    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 inAppNotificationService.archive(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/in-app/:id/unarchive
   * Unarchive a notification
   */
  router.post(
    '/:id/unarchive',
    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 inAppNotificationService.unarchive(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/in-app/archive-all
   * Archive all notifications for current user
   */
  router.post(
    '/archive-all',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const count = await inAppNotificationService.archiveAll(ctx, ctx.userId);

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

  /**
   * POST /notifications/in-app/archive-bulk
   * Archive multiple notifications
   */
  router.post(
    '/archive-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 inAppNotificationService.archiveMultiple(ctx, ids);

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

  /**
   * DELETE /notifications/in-app/:id
   * Delete a notification
   */
  router.delete(
    '/: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 deleted = await inAppNotificationService.delete(ctx, req.params.id);
        if (!deleted) {
          res.status(404).json({ error: 'Not Found', message: 'Notification not found' });
          return;
        }

        res.status(200).json({ success: true, message: 'Notification deleted' });
      } catch (error) {
        next(error);
      }
    },
  );

  /**
   * DELETE /notifications/in-app/archived
   * Delete all archived notifications for current user
   */
  router.delete(
    '/archived',
    authMiddleware.authenticate,
    async (req: Request, res: Response, next: NextFunction): Promise<void> => {
      try {
        const ctx = getContext(req);
        if (!ctx.tenantId || !ctx.userId) {
          res.status(400).json({ error: 'Bad Request', message: 'Tenant ID and User ID required' });
          return;
        }

        const count = await inAppNotificationService.deleteArchived(ctx, ctx.userId);

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

  // ============= Admin Routes =============

  /**
   * GET /notifications/in-app/admin/all
   * Get all in-app notifications (admin)
   */
  router.get(
    '/admin/all',
    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 filters: InAppNotificationFilters = {
          userId: req.query.userId as string,
          category: req.query.category as InAppCategory,
          priority: req.query.priority as InAppPriority,
          isRead: req.query.isRead === 'true' ? true : req.query.isRead === 'false' ? false : undefined,
          isArchived: req.query.isArchived === 'true' ? true : req.query.isArchived === 'false' ? false : undefined,
          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 inAppNotificationService.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);
      }
    },
  );

  /**
   * POST /notifications/in-app/admin/create
   * Create an in-app notification (admin)
   */
  router.post(
    '/admin/create',
    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 dto: CreateInAppNotificationDto = req.body;

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

        const notification = await inAppNotificationService.create(ctx, dto);

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

  /**
   * POST /notifications/in-app/admin/broadcast
   * Broadcast notification to multiple users (admin)
   */
  router.post(
    '/admin/broadcast',
    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 { userIds, title, message, category, priority, actionUrl } = req.body;

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

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

        const count = await inAppNotificationService.broadcastToTenant(ctx, userIds, {
          title,
          message,
          category,
          priority,
          actionUrl,
        });

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

  /**
   * GET /notifications/in-app/admin/by-context/:contextType/:contextId
   * Get notifications by context (admin)
   */
  router.get(
    '/admin/by-context/:contextType/:contextId',
    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 notifications = await inAppNotificationService.findByContext(
          ctx,
          req.params.contextType,
          req.params.contextId,
        );

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

  /**
   * DELETE /notifications/in-app/admin/by-context/:contextType/:contextId
   * Delete notifications by context (admin)
   */
  router.delete(
    '/admin/by-context/:contextType/:contextId',
    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 count = await inAppNotificationService.deleteByContext(
          ctx,
          req.params.contextType,
          req.params.contextId,
        );

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

  return router;
}

export default createInAppNotificationController;
