/**
 * Biometric Sync Service
 * ERP Construccion - Modulo Biometrics
 *
 * Logica de negocio para sincronizacion de datos biometricos
 * entre dispositivos moviles y el servidor.
 */

import { DataSource } from 'typeorm';
import { DeviceService } from './device.service';
import { BiometricCredentialService } from './biometric-credential.service';

// DTOs
export interface SyncRequestDto {
  deviceId: string;
  deviceUuid: string;
  lastSyncTimestamp?: Date;
  pendingActivities?: PendingActivityDto[];
  currentLocation?: {
    latitude: number;
    longitude: number;
  };
}

export interface PendingActivityDto {
  activityType: string;
  activityStatus: string;
  timestamp: Date;
  details?: Record<string, any>;
  latitude?: number;
  longitude?: number;
}

export interface SyncResponseDto {
  success: boolean;
  syncTimestamp: Date;
  serverTime: Date;
  deviceConfig?: DeviceConfigDto;
  credentials?: CredentialSyncDto[];
  activeSessions?: SessionSyncDto[];
  pendingActions?: PendingActionDto[];
  warnings?: string[];
}

export interface DeviceConfigDto {
  biometricEnabled: boolean;
  biometricType?: string;
  isTrusted: boolean;
  trustLevel: number;
  sessionTimeout: number;
  requireBiometricForSensitive: boolean;
  allowOfflineAuth: boolean;
  maxOfflineAuthHours: number;
  syncIntervalMinutes: number;
}

export interface CredentialSyncDto {
  id: string;
  biometricType: string;
  credentialName?: string;
  isPrimary: boolean;
  isActive: boolean;
  lastUsedAt?: Date;
}

export interface SessionSyncDto {
  id: string;
  authMethod: string;
  issuedAt: Date;
  expiresAt: Date;
  isCurrent: boolean;
}

export interface PendingActionDto {
  actionType: 'revoke_session' | 'update_credential' | 'force_logout' | 'require_reauth';
  actionId: string;
  payload?: Record<string, any>;
  priority: 'low' | 'normal' | 'high' | 'critical';
  expiresAt?: Date;
}

export interface AttendanceRecordDto {
  employeeId: string;
  deviceId: string;
  checkType: 'check_in' | 'check_out' | 'break_start' | 'break_end';
  timestamp: Date;
  biometricMethod?: string;
  latitude?: number;
  longitude?: number;
  photoUrl?: string;
  projectId?: string;
  notes?: string;
}

export interface BulkSyncResultDto {
  totalRecords: number;
  successCount: number;
  failedCount: number;
  errors: Array<{
    index: number;
    error: string;
    record?: any;
  }>;
  syncedAt: Date;
}

export class BiometricSyncService {
  private deviceService: DeviceService;
  private credentialService: BiometricCredentialService;

  constructor(dataSource: DataSource) {
    this.deviceService = new DeviceService(dataSource);
    this.credentialService = new BiometricCredentialService(dataSource);
  }

  // ============================================
  // DEVICE SYNC
  // ============================================

  /**
   * Perform full device sync
   */
  async syncDevice(tenantId: string, request: SyncRequestDto): Promise<SyncResponseDto> {
    const now = new Date();
    const warnings: string[] = [];

    // Get device
    const device = await this.deviceService.findById(tenantId, request.deviceId);
    if (!device) {
      return {
        success: false,
        syncTimestamp: now,
        serverTime: now,
        warnings: ['Device not found'],
      };
    }

    // Verify device UUID matches
    if (device.deviceUuid !== request.deviceUuid) {
      return {
        success: false,
        syncTimestamp: now,
        serverTime: now,
        warnings: ['Device UUID mismatch - possible security issue'],
      };
    }

    // Update device location if provided
    if (request.currentLocation) {
      await this.deviceService.updateLocation(
        tenantId,
        request.deviceId,
        request.currentLocation.latitude,
        request.currentLocation.longitude
      );
    }

    // Process pending activities
    if (request.pendingActivities && request.pendingActivities.length > 0) {
      await this.processPendingActivities(request.deviceId, device.userId, request.pendingActivities);
    }

    // Get credentials for device
    const credentials = await this.credentialService.findByDevice(request.deviceId);
    const credentialSync: CredentialSyncDto[] = credentials.map(c => ({
      id: c.id,
      biometricType: c.biometricType,
      credentialName: c.credentialName,
      isPrimary: c.isPrimary,
      isActive: c.isActive,
      lastUsedAt: c.lastUsedAt,
    }));

    // Get active sessions
    const sessions = await this.deviceService.getActiveSessions(tenantId, request.deviceId);
    const sessionSync: SessionSyncDto[] = sessions.map(s => ({
      id: s.id,
      authMethod: s.authMethod,
      issuedAt: s.issuedAt,
      expiresAt: s.expiresAt,
      isCurrent: true,
    }));

    // Build device config
    const deviceConfig: DeviceConfigDto = {
      biometricEnabled: device.biometricEnabled,
      biometricType: device.biometricType,
      isTrusted: device.isTrusted,
      trustLevel: device.trustLevel,
      sessionTimeout: 30 * 60 * 1000, // 30 minutes default
      requireBiometricForSensitive: true,
      allowOfflineAuth: device.isTrusted,
      maxOfflineAuthHours: device.trustLevel >= 2 ? 24 : 4,
      syncIntervalMinutes: 15,
    };

    // Check for pending actions
    const pendingActions = await this.getPendingActions(tenantId, request.deviceId);

    // Add warnings if needed
    if (!device.biometricEnabled && credentials.length === 0) {
      warnings.push('No biometric credentials configured');
    }
    if (sessions.length === 0) {
      warnings.push('No active sessions');
    }

    return {
      success: true,
      syncTimestamp: now,
      serverTime: now,
      deviceConfig,
      credentials: credentialSync,
      activeSessions: sessionSync,
      pendingActions,
      warnings: warnings.length > 0 ? warnings : undefined,
    };
  }

  /**
   * Process pending activities from device
   */
  private async processPendingActivities(
    deviceId: string,
    userId: string,
    activities: PendingActivityDto[]
  ): Promise<void> {
    for (const activity of activities) {
      await this.deviceService.logActivity({
        deviceId,
        userId,
        activityType: activity.activityType as any,
        activityStatus: activity.activityStatus as any,
        details: {
          ...activity.details,
          originalTimestamp: activity.timestamp,
          syncedAt: new Date(),
        },
        latitude: activity.latitude,
        longitude: activity.longitude,
      });
    }
  }

  /**
   * Get pending actions for device
   */
  private async getPendingActions(_tenantId: string, _deviceId: string): Promise<PendingActionDto[]> {
    // This would typically check a pending_actions table
    // For now, return empty array
    return [];
  }

  // ============================================
  // ATTENDANCE SYNC
  // ============================================

  /**
   * Sync attendance records from device
   */
  async syncAttendanceRecords(
    tenantId: string,
    deviceId: string,
    records: AttendanceRecordDto[]
  ): Promise<BulkSyncResultDto> {
    const result: BulkSyncResultDto = {
      totalRecords: records.length,
      successCount: 0,
      failedCount: 0,
      errors: [],
      syncedAt: new Date(),
    };

    // Verify device exists
    const device = await this.deviceService.findById(tenantId, deviceId);
    if (!device) {
      result.failedCount = records.length;
      result.errors.push({
        index: -1,
        error: 'Device not found',
      });
      return result;
    }

    // Process each record
    for (let i = 0; i < records.length; i++) {
      const record = records[i];
      try {
        await this.processAttendanceRecord(tenantId, record);
        result.successCount++;

        // Log activity
        await this.deviceService.logActivity({
          deviceId: record.deviceId,
          userId: record.employeeId,
          activityType: 'biometric_auth',
          activityStatus: 'success',
          details: {
            checkType: record.checkType,
            biometricMethod: record.biometricMethod,
            projectId: record.projectId,
          },
          latitude: record.latitude,
          longitude: record.longitude,
        });
      } catch (error) {
        result.failedCount++;
        result.errors.push({
          index: i,
          error: (error as Error).message,
          record: { employeeId: record.employeeId, timestamp: record.timestamp },
        });
      }
    }

    return result;
  }

  /**
   * Process individual attendance record
   */
  private async processAttendanceRecord(_tenantId: string, record: AttendanceRecordDto): Promise<void> {
    // This would typically insert into hr.attendance_records table
    // For now, just validate the record
    if (!record.employeeId) {
      throw new Error('Employee ID is required');
    }
    if (!record.timestamp) {
      throw new Error('Timestamp is required');
    }
    if (!record.checkType) {
      throw new Error('Check type is required');
    }

    // In a real implementation:
    // 1. Verify employee exists and is active
    // 2. Validate check sequence (can't check out without checking in)
    // 3. Apply business rules (overtime, breaks, etc.)
    // 4. Store in attendance_records table
  }

  // ============================================
  // BULK OPERATIONS
  // ============================================

  /**
   * Sync multiple devices at once (for batch operations)
   */
  async bulkDeviceSync(
    tenantId: string,
    requests: SyncRequestDto[]
  ): Promise<Map<string, SyncResponseDto>> {
    const results = new Map<string, SyncResponseDto>();

    for (const request of requests) {
      const response = await this.syncDevice(tenantId, request);
      results.set(request.deviceId, response);
    }

    return results;
  }

  /**
   * Force sync all devices for a user
   */
  async forceSyncUserDevices(tenantId: string, userId: string): Promise<{
    devicesUpdated: number;
    errors: string[];
  }> {
    const devices = await this.deviceService.findByUser(tenantId, userId);
    let devicesUpdated = 0;
    const errors: string[] = [];

    for (const device of devices.data) {
      try {
        // Update last sync trigger
        await this.deviceService.update(tenantId, device.id, {
          lastIpAddress: device.lastIpAddress, // Just trigger an update
        });
        devicesUpdated++;
      } catch (error) {
        errors.push(`Device ${device.id}: ${(error as Error).message}`);
      }
    }

    return { devicesUpdated, errors };
  }

  // ============================================
  // OFFLINE SUPPORT
  // ============================================

  /**
   * Generate offline authentication token
   */
  async generateOfflineToken(
    tenantId: string,
    deviceId: string,
    credentialId: string
  ): Promise<{
    token: string;
    expiresAt: Date;
    maxUses: number;
  } | null> {
    const device = await this.deviceService.findById(tenantId, deviceId);
    if (!device || !device.isTrusted) {
      return null;
    }

    const credential = await this.credentialService.findByCredentialId(credentialId);
    if (!credential || credential.deviceId !== deviceId) {
      return null;
    }

    // Calculate expiration based on trust level
    const hoursValid = device.trustLevel >= 3 ? 48 : device.trustLevel >= 2 ? 24 : 8;
    const expiresAt = new Date(Date.now() + hoursValid * 60 * 60 * 1000);
    const maxUses = device.trustLevel >= 2 ? 10 : 3;

    // In real implementation, generate a signed JWT or similar
    const token = `offline_${deviceId}_${Date.now()}_${Math.random().toString(36).substring(7)}`;

    return {
      token,
      expiresAt,
      maxUses,
    };
  }

  /**
   * Validate offline authentication
   */
  async validateOfflineAuth(
    deviceId: string,
    token: string,
    _signature: string
  ): Promise<{
    valid: boolean;
    reason?: string;
    remainingUses?: number;
  }> {
    // In real implementation:
    // 1. Verify token signature
    // 2. Check expiration
    // 3. Check remaining uses
    // 4. Verify device binding

    // For now, basic validation
    if (!token.startsWith('offline_')) {
      return { valid: false, reason: 'Invalid token format' };
    }

    const parts = token.split('_');
    if (parts[1] !== deviceId) {
      return { valid: false, reason: 'Token not bound to this device' };
    }

    const timestamp = parseInt(parts[2], 10);
    const age = Date.now() - timestamp;
    const maxAge = 48 * 60 * 60 * 1000; // 48 hours max

    if (age > maxAge) {
      return { valid: false, reason: 'Token expired' };
    }

    return { valid: true, remainingUses: 5 };
  }

  // ============================================
  // HEALTH & DIAGNOSTICS
  // ============================================

  /**
   * Check sync health for tenant
   */
  async getSyncHealth(tenantId: string): Promise<{
    healthy: boolean;
    activeDevices: number;
    staleDevices: number;
    lastSyncErrors: number;
    recommendations: string[];
  }> {
    const stats = await this.deviceService.getStatistics(tenantId);
    const staleThreshold = new Date(Date.now() - 24 * 60 * 60 * 1000);

    // Count stale devices (not synced in 24 hours)
    const allDevices = await this.deviceService.findAll(tenantId, { isActive: true });
    const staleDevices = allDevices.data.filter(d => d.lastSeenAt < staleThreshold).length;

    const recommendations: string[] = [];
    if (staleDevices > 0) {
      recommendations.push(`${staleDevices} devices have not synced in 24 hours`);
    }
    if (stats.biometricEnabled < stats.activeDevices * 0.5) {
      recommendations.push('Less than 50% of devices have biometrics enabled');
    }
    if (stats.trustedDevices < stats.activeDevices * 0.3) {
      recommendations.push('Consider trusting more devices to improve user experience');
    }

    return {
      healthy: staleDevices === 0 && recommendations.length === 0,
      activeDevices: stats.activeDevices,
      staleDevices,
      lastSyncErrors: 0, // Would track from error log
      recommendations,
    };
  }
}
