/**
 * Offline Sync Controller
 *
 * REST API endpoints for offline data synchronization
 *
 * @module Mobile
 */

import { Router, Request, Response, NextFunction } from 'express';
import { DataSource } from 'typeorm';
import { OfflineSyncService } from '../services/offline-sync.service';
import {
  CreateSyncQueueItemDto,
  ProcessSyncBatchDto,
  ResolveSyncConflictDto,
  SyncFilterDto,
} from '../dto/offline-sync.dto';

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

export class OfflineSyncController {
  public router: Router;
  private service: OfflineSyncService;

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

  private initializeRoutes(): void {
    // Sync queue management
    this.router.get('/queue', this.findAll.bind(this));
    this.router.get('/queue/:id', this.getById.bind(this));
    this.router.post('/queue', this.queueItem.bind(this));
    this.router.post('/queue/batch', this.queueBatch.bind(this));

    // Processing
    this.router.get('/status/:deviceId', this.getSyncStatus.bind(this));
    this.router.get('/pending/:deviceId', this.getPendingItems.bind(this));
    this.router.post('/process/:deviceId', this.processQueue.bind(this));
    this.router.post('/retry/:deviceId', this.retryFailedItems.bind(this));

    // Conflicts
    this.router.get('/conflicts', this.getConflicts.bind(this));
    this.router.get('/conflicts/:id', this.getConflictById.bind(this));
    this.router.post('/conflicts/:id/resolve', this.resolveConflict.bind(this));

    // Maintenance
    this.router.post('/cleanup', this.cleanupOldItems.bind(this));
  }

  /**
   * GET /mobile/sync/queue
   * Get queue items with filters
   */
  private async findAll(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const filter: SyncFilterDto = {
        userId: req.query.userId as string,
        deviceId: req.query.deviceId as string,
        sessionId: req.query.sessionId as string,
        entityType: req.query.entityType as string,
        status: req.query.status as any,
        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.findAll(
        { tenantId: req.tenantId! },
        filter
      );

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

  /**
   * GET /mobile/sync/queue/:id
   * Get queue item by ID
   */
  private async getById(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const item = await this.service.findById(
        { tenantId: req.tenantId! },
        req.params.id
      );

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

      res.json({ data: this.service.toQueueResponseDto(item) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/queue
   * Add item to sync queue
   */
  private async queueItem(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: CreateSyncQueueItemDto = req.body;
      const item = await this.service.queueItem(
        { tenantId: req.tenantId! },
        dto
      );
      res.status(201).json({ data: this.service.toQueueResponseDto(item) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/queue/batch
   * Queue batch of items
   */
  private async queueBatch(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: ProcessSyncBatchDto = req.body;
      const items = await this.service.queueBatch(
        { tenantId: req.tenantId! },
        dto
      );
      res.status(201).json({
        data: items.map(item => this.service.toQueueResponseDto(item)),
        count: items.length,
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/sync/status/:deviceId
   * Get sync status for device
   */
  private async getSyncStatus(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const status = await this.service.getSyncStatus(
        { tenantId: req.tenantId! },
        req.params.deviceId
      );
      res.json({ data: status });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/sync/pending/:deviceId
   * Get pending items for device
   */
  private async getPendingItems(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 100;
      const items = await this.service.getPendingItems(
        { tenantId: req.tenantId! },
        req.params.deviceId,
        limit
      );
      res.json({
        data: items.map(item => this.service.toQueueResponseDto(item)),
        count: items.length,
      });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/process/:deviceId
   * Process sync queue for device
   */
  private async processQueue(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const result = await this.service.processQueue(
        { tenantId: req.tenantId!, userId: req.userId },
        req.params.deviceId
      );
      res.json({ data: result });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/retry/:deviceId
   * Retry failed items for device
   */
  private async retryFailedItems(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const count = await this.service.retryFailedItems(
        { tenantId: req.tenantId! },
        req.params.deviceId
      );
      res.json({ data: { retried: count } });
    } catch (error) {
      next(error);
    }
  }

  /**
   * GET /mobile/sync/conflicts
   * Get unresolved conflicts
   */
  private async getConflicts(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const deviceId = req.query.deviceId as string;
      const conflicts = await this.service.getConflicts(
        { tenantId: req.tenantId! },
        deviceId
      );
      res.json({
        data: conflicts.map(c => this.service.toConflictResponseDto(c)),
        count: conflicts.length,
      });
    } catch (error) {
      next(error);
    }
  }

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

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

      res.json({ data: this.service.toConflictResponseDto(conflict) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/conflicts/:id/resolve
   * Resolve conflict
   */
  private async resolveConflict(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const dto: ResolveSyncConflictDto = req.body;
      const conflict = await this.service.resolveConflict(
        { tenantId: req.tenantId!, userId: req.userId },
        req.params.id,
        dto
      );
      res.json({ data: this.service.toConflictResponseDto(conflict) });
    } catch (error) {
      next(error);
    }
  }

  /**
   * POST /mobile/sync/cleanup
   * Cleanup old processed items
   */
  private async cleanupOldItems(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
    try {
      const olderThanDays = req.body.olderThanDays || 30;
      const count = await this.service.cleanupOldItems(
        { tenantId: req.tenantId! },
        olderThanDays
      );
      res.json({ data: { deleted: count } });
    } catch (error) {
      next(error);
    }
  }
}
