/**
 * Device Registration Service
 *
 * Service for managing mobile device registration and app configuration
 *
 * @module Mobile
 */

import { Repository, DataSource } from 'typeorm';
import { MobileSession } from '../entities/mobile-session.entity';
import { PushToken } from '../entities/push-token.entity';
import { ServiceContext } from './mobile-session.service';

export interface DeviceInfo {
  deviceId: string;
  platform: string;
  osVersion?: string;
  appVersion?: string;
  model?: string;
  manufacturer?: string;
}

export interface AppConfig {
  syncIntervalSeconds: number;
  offlineModeEnabled: boolean;
  maxOfflineHours: number;
  locationTrackingEnabled: boolean;
  locationIntervalSeconds: number;
  pushNotificationsEnabled: boolean;
  features: Record<string, boolean>;
  apiEndpoints: Record<string, string>;
  minAppVersion: string;
  latestAppVersion: string;
  forceUpdate: boolean;
  maintenanceMode: boolean;
  maintenanceMessage?: string;
}

export interface RegisterDeviceDto {
  userId: string;
  deviceId: string;
  platform: string;
  osVersion?: string;
  appVersion?: string;
  model?: string;
  manufacturer?: string;
  pushToken?: string;
  pushProvider?: 'firebase' | 'apns' | 'fcm';
}

export interface DeviceRegistrationResult {
  sessionId: string;
  config: AppConfig;
  updateRequired: boolean;
  updateMessage?: string;
}

export class DeviceRegistrationService {
  private sessionRepository: Repository<MobileSession>;
  private tokenRepository: Repository<PushToken>;

  constructor(dataSource: DataSource) {
    this.sessionRepository = dataSource.getRepository(MobileSession);
    this.tokenRepository = dataSource.getRepository(PushToken);
  }

  /**
   * Register a device
   */
  async registerDevice(ctx: ServiceContext, dto: RegisterDeviceDto): Promise<DeviceRegistrationResult> {
    // Terminate existing sessions for this device
    await this.sessionRepository.update(
      { deviceId: dto.deviceId, status: 'active' },
      { status: 'terminated', endedAt: new Date() }
    );

    // Create new session
    const session = this.sessionRepository.create({
      userId: dto.userId,
      deviceId: dto.deviceId,
      tenantId: ctx.tenantId,
      platform: dto.platform,
      osVersion: dto.osVersion,
      appVersion: dto.appVersion,
      status: 'active',
      isOfflineMode: false,
      pendingSyncCount: 0,
      startedAt: new Date(),
      lastActivityAt: new Date(),
      expiresAt: this.calculateExpirationDate(),
    });

    await this.sessionRepository.save(session);

    // Register push token if provided
    if (dto.pushToken) {
      await this.registerPushToken(ctx, {
        userId: dto.userId,
        deviceId: dto.deviceId,
        token: dto.pushToken,
        platform: dto.platform,
        provider: dto.pushProvider || 'firebase',
      });
    }

    // Get app config
    const config = await this.getAppConfig(ctx, dto.platform);

    // Check if update is required
    const updateRequired = this.checkUpdateRequired(dto.appVersion, config);

    return {
      sessionId: session.id,
      config,
      updateRequired,
      updateMessage: updateRequired ? this.getUpdateMessage(dto.appVersion, config) : undefined,
    };
  }

  /**
   * Unregister a device
   */
  async unregisterDevice(ctx: ServiceContext, deviceId: string): Promise<void> {
    // Terminate all sessions
    await this.sessionRepository.update(
      { deviceId, tenantId: ctx.tenantId, status: 'active' },
      { status: 'terminated', endedAt: new Date() }
    );

    // Deactivate push tokens
    await this.tokenRepository.update(
      { deviceId, tenantId: ctx.tenantId },
      { isActive: false }
    );
  }

  /**
   * Get app configuration
   */
  async getAppConfig(_ctx: ServiceContext, _platform?: string): Promise<AppConfig> {
    // In production, this would be loaded from database/settings
    // For now, return default configuration
    return {
      syncIntervalSeconds: 300, // 5 minutes
      offlineModeEnabled: true,
      maxOfflineHours: 72, // 3 days
      locationTrackingEnabled: true,
      locationIntervalSeconds: 60, // 1 minute
      pushNotificationsEnabled: true,
      features: {
        offlineMode: true,
        locationTracking: true,
        pushNotifications: true,
        barcodeScan: true,
        cameraCapture: true,
        biometricAuth: true,
        darkMode: true,
      },
      apiEndpoints: {
        auth: '/api/v1/auth',
        sync: '/api/v1/mobile/sync',
        push: '/api/v1/mobile/push',
        sessions: '/api/v1/mobile/sessions',
      },
      minAppVersion: '1.0.0',
      latestAppVersion: '1.2.0',
      forceUpdate: false,
      maintenanceMode: false,
    };
  }

  /**
   * Check device status
   */
  async checkDeviceStatus(ctx: ServiceContext, deviceId: string): Promise<{
    registered: boolean;
    activeSession: boolean;
    sessionId?: string;
    lastActivityAt?: Date;
  }> {
    const session = await this.sessionRepository.findOne({
      where: { deviceId, tenantId: ctx.tenantId, status: 'active' },
      order: { startedAt: 'DESC' },
    });

    return {
      registered: session !== null,
      activeSession: session !== null && session.status === 'active',
      sessionId: session?.id,
      lastActivityAt: session?.lastActivityAt,
    };
  }

  /**
   * Get registered devices for user
   */
  async getUserDevices(ctx: ServiceContext, userId: string): Promise<DeviceInfo[]> {
    const sessions = await this.sessionRepository.find({
      where: { userId, tenantId: ctx.tenantId },
      order: { lastActivityAt: 'DESC' },
    });

    // Group by device and get latest session for each
    const deviceMap = new Map<string, MobileSession>();
    for (const session of sessions) {
      if (!deviceMap.has(session.deviceId)) {
        deviceMap.set(session.deviceId, session);
      }
    }

    return Array.from(deviceMap.values()).map(session => ({
      deviceId: session.deviceId,
      platform: session.platform || 'unknown',
      osVersion: session.osVersion,
      appVersion: session.appVersion,
    }));
  }

  /**
   * Revoke device access
   */
  async revokeDeviceAccess(ctx: ServiceContext, userId: string, deviceId: string): Promise<void> {
    // Terminate sessions
    await this.sessionRepository.update(
      { userId, deviceId, tenantId: ctx.tenantId, status: 'active' },
      { status: 'terminated', endedAt: new Date() }
    );

    // Deactivate push tokens
    await this.tokenRepository.update(
      { userId, deviceId, tenantId: ctx.tenantId },
      { isActive: false }
    );
  }

  /**
   * Revoke all devices for user
   */
  async revokeAllDevices(ctx: ServiceContext, userId: string): Promise<number> {
    const result = await this.sessionRepository.update(
      { userId, tenantId: ctx.tenantId, status: 'active' },
      { status: 'terminated', endedAt: new Date() }
    );

    await this.tokenRepository.update(
      { userId, tenantId: ctx.tenantId },
      { isActive: false }
    );

    return result.affected || 0;
  }

  /**
   * Update device info
   */
  async updateDeviceInfo(
    ctx: ServiceContext,
    sessionId: string,
    info: Partial<DeviceInfo>
  ): Promise<MobileSession> {
    const session = await this.sessionRepository.findOne({
      where: { id: sessionId, tenantId: ctx.tenantId },
    });

    if (!session) {
      throw new Error('Session not found');
    }

    if (info.platform) session.platform = info.platform;
    if (info.osVersion) session.osVersion = info.osVersion;
    if (info.appVersion) session.appVersion = info.appVersion;
    session.lastActivityAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Register push token (internal)
   */
  private async registerPushToken(
    ctx: ServiceContext,
    dto: {
      userId: string;
      deviceId: string;
      token: string;
      platform: string;
      provider: 'firebase' | 'apns' | 'fcm';
    }
  ): Promise<PushToken> {
    // Check for existing token
    let token = await this.tokenRepository.findOne({
      where: {
        deviceId: dto.deviceId,
        platform: dto.platform,
        tenantId: ctx.tenantId,
      },
    });

    if (token) {
      token.token = dto.token;
      token.userId = dto.userId;
      token.provider = dto.provider;
      token.isActive = true;
      token.isValid = true;
      token.invalidReason = undefined as any;
    } else {
      token = this.tokenRepository.create({
        userId: dto.userId,
        deviceId: dto.deviceId,
        tenantId: ctx.tenantId,
        token: dto.token,
        platform: dto.platform,
        provider: dto.provider,
        isActive: true,
        isValid: true,
        subscribedTopics: [],
      });
    }

    return this.tokenRepository.save(token);
  }

  /**
   * Check if update is required
   */
  private checkUpdateRequired(currentVersion: string | undefined, config: AppConfig): boolean {
    if (!currentVersion) {
      return true;
    }

    return this.compareVersions(currentVersion, config.minAppVersion) < 0;
  }

  /**
   * Get update message
   */
  private getUpdateMessage(_currentVersion: string | undefined, config: AppConfig): string {
    if (config.forceUpdate) {
      return `Please update to version ${config.latestAppVersion} to continue using the app.`;
    }
    return `A new version (${config.latestAppVersion}) is available. Please update for the best experience.`;
  }

  /**
   * Compare semantic versions
   */
  private compareVersions(v1: string, v2: string): number {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);

    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
      const p1 = parts1[i] || 0;
      const p2 = parts2[i] || 0;

      if (p1 > p2) return 1;
      if (p1 < p2) return -1;
    }

    return 0;
  }

  /**
   * Calculate session expiration date (24 hours)
   */
  private calculateExpirationDate(): Date {
    const date = new Date();
    date.setHours(date.getHours() + 24);
    return date;
  }
}
