/**
 * Push Notification Controller
 *
 * REST API endpoints for push notifications and token management
 *
 * @module Mobile
 */

import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import { PushNotificationService } from '../services/push-notification.service';
import {
  RegisterPushTokenDto,
  UpdatePushTokenDto,
  SubscribeTopicsDto,
  SendPushNotificationDto,
  SendBulkNotificationDto,
  NotificationFilterDto,
} from '../dto/push-notification.dto';

interface AuthenticatedRequest extends Request {
  tenantId?: string;
  userId?: string;
}

export class PushNotificationController {
  public router: Router;
  private service: PushNotificationService;

  constructor(dataSource: DataSource) {
    this.router = Router();
    this.service = new PushNotificationService(dataSource);
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Token management
    this.router.post('/tokens', this.registerToken.bind(this));
    this.router.get('/tokens/:id', this.getToken.bind(this));
    this.router.get('/tokens/user/:userId', this.getTokensByUser.bind(this));
    this.router.get('/tokens/device/:deviceId', this.getTokenByDevice.bind(this));
    this.router.put('/tokens/:id', this.updateToken.bind(this));
    this.router.delete('/tokens/:id', this.deactivateToken.bind(this));
    this.router.delete('/tokens/device/:deviceId', this.deactivateDeviceTokens.bind(this));

    // Topic subscriptions
    this.router.post('/tokens/:id/subscribe', this.subscribeToTopics.bind(this));
    this.router.post('/tokens/:id/unsubscribe', this.unsubscribeFromTopics.bind(this));

    // Send notifications
    this.router.post('/send/user', this.sendToUser.bind(this));
    this.router.post('/send/device', this.sendToDevice.bind(this));
    this.router.post('/send/bulk', this.sendBulk.bind(this));

    // Notification log
    this.router.get('/log', this.getNotificationLog.bind(this));
    this.router.post('/log/:id/delivered', this.markDelivered.bind(this));
    this.router.post('/log/:id/read', this.markRead.bind(this));

    // Statistics
    this.router.get('/stats', this.getStats.bind(this));
  }

  /**
   * POST /mobile/push/tokens
   * Register push token
   */
  private async registerToken(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: RegisterPushTokenDto = req.body;
      const token = await this.service.registerToken({ tenantId: req.tenantId! }, dto);
      res.status(201).json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/push/tokens/:id
   * Get token by ID
   */
  private async getToken(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const token = await this.service.findTokenById(
        { tenantId: req.tenantId! },
        req.params.id
      );

      if (!token) {
        res.status(404).json({ error: 'Token not found' });
        return;
      }

      res.json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/push/tokens/user/:userId
   * Get tokens for user
   */
  private async getTokensByUser(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const tokens = await this.service.findTokensByUser(
        { tenantId: req.tenantId! },
        req.params.userId
      );
      res.json({ data: tokens.map(t => this.service.toTokenResponseDto(t)) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/push/tokens/device/:deviceId
   * Get token for device
   */
  private async getTokenByDevice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const token = await this.service.findTokenByDevice(
        { tenantId: req.tenantId! },
        req.params.deviceId
      );

      if (!token) {
        res.status(404).json({ error: 'Token not found for device' });
        return;
      }

      res.json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * PUT /mobile/push/tokens/:id
   * Update token
   */
  private async updateToken(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: UpdatePushTokenDto = req.body;
      const token = await this.service.updateToken(
        { tenantId: req.tenantId! },
        req.params.id,
        dto
      );
      res.json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * DELETE /mobile/push/tokens/:id
   * Deactivate token
   */
  private async deactivateToken(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      await this.service.deactivateToken(
        { tenantId: req.tenantId! },
        req.params.id
      );
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  /**
   * DELETE /mobile/push/tokens/device/:deviceId
   * Deactivate all tokens for device
   */
  private async deactivateDeviceTokens(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const count = await this.service.deactivateDeviceTokens(
        { tenantId: req.tenantId! },
        req.params.deviceId
      );
      res.json({ data: { deactivated: count } });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/tokens/:id/subscribe
   * Subscribe to topics
   */
  private async subscribeToTopics(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: SubscribeTopicsDto = req.body;
      const token = await this.service.subscribeToTopics(
        { tenantId: req.tenantId! },
        req.params.id,
        dto
      );
      res.json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/tokens/:id/unsubscribe
   * Unsubscribe from topics
   */
  private async unsubscribeFromTopics(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const { topics } = req.body;
      const token = await this.service.unsubscribeFromTopics(
        { tenantId: req.tenantId! },
        req.params.id,
        topics
      );
      res.json({ data: this.service.toTokenResponseDto(token) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/send/user
   * Send notification to user
   */
  private async sendToUser(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: SendPushNotificationDto = req.body;
      const results = await this.service.sendToUser(
        { tenantId: req.tenantId! },
        dto
      );
      res.json({ data: results });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/send/device
   * Send notification to device
   */
  private async sendToDevice(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: SendPushNotificationDto = req.body;
      const result = await this.service.sendToDevice(
        { tenantId: req.tenantId! },
        dto
      );

      if (!result) {
        res.status(404).json({ error: 'No active token found for device' });
        return;
      }

      res.json({ data: result });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/send/bulk
   * Send bulk notification
   */
  private async sendBulk(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: SendBulkNotificationDto = req.body;
      const result = await this.service.sendBulk(
        { tenantId: req.tenantId! },
        dto
      );
      res.json({ data: result });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/push/log
   * Get notification log
   */
  private async getNotificationLog(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const filter: NotificationFilterDto = {
        userId: req.query.userId as string,
        deviceId: req.query.deviceId as string,
        category: req.query.category as any,
        status: req.query.status as any,
        startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
        endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
        limit: req.query.limit ? parseInt(req.query.limit as string, 10) : undefined,
        offset: req.query.offset ? parseInt(req.query.offset as string, 10) : undefined,
      };

      const result = await this.service.getNotificationLog(
        { tenantId: req.tenantId! },
        filter
      );

      res.json({
        data: result.data.map(log => this.service.toLogResponseDto(log)),
        total: result.total,
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/log/:id/delivered
   * Mark notification as delivered
   */
  private async markDelivered(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      await this.service.markDelivered(
        { tenantId: req.tenantId! },
        req.params.id
      );
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/push/log/:id/read
   * Mark notification as read
   */
  private async markRead(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      await this.service.markRead(
        { tenantId: req.tenantId! },
        req.params.id
      );
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/push/stats
   * Get notification statistics
   */
  private async getStats(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const filter: NotificationFilterDto = {
        startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
        endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
      };

      const stats = await this.service.getStats(
        { tenantId: req.tenantId! },
        filter
      );

      res.json({ data: stats });
    } catch (error) {
      next(error);
    }
  }
}
