/**
 * Device Service
 * ERP Construccion - Modulo Biometrics
 *
 * Logica de negocio para gestion de dispositivos biometricos.
 */

import { Repository, DataSource, IsNull, LessThan, MoreThan } from 'typeorm';
import { Device, DevicePlatform, BiometricType } from '../entities/device.entity';
import { DeviceSession, AuthMethod } from '../entities/device-session.entity';
import { DeviceActivityLog, ActivityType, ActivityStatus } from '../entities/device-activity-log.entity';

// DTOs
export interface RegisterDeviceDto {
  userId: string;
  deviceUuid: string;
  deviceName?: string;
  deviceModel?: string;
  deviceBrand?: string;
  platform: DevicePlatform;
  platformVersion?: string;
  appVersion?: string;
  pushToken?: string;
  ipAddress?: string;
  userAgent?: string;
  latitude?: number;
  longitude?: number;
}

export interface UpdateDeviceDto {
  deviceName?: string;
  appVersion?: string;
  pushToken?: string;
  isActive?: boolean;
  isTrusted?: boolean;
  trustLevel?: number;
  biometricEnabled?: boolean;
  biometricType?: BiometricType;
  lastLatitude?: number;
  lastLongitude?: number;
  lastIpAddress?: string;
  lastUserAgent?: string;
}

export interface DeviceFilters {
  userId?: string;
  platform?: DevicePlatform;
  isActive?: boolean;
  isTrusted?: boolean;
  biometricEnabled?: boolean;
  search?: string;
}

export interface CreateSessionDto {
  deviceId: string;
  userId: string;
  accessTokenHash: string;
  refreshTokenHash?: string;
  authMethod: AuthMethod;
  expiresAt: Date;
  refreshExpiresAt?: Date;
  ipAddress?: string;
  userAgent?: string;
  latitude?: number;
  longitude?: number;
}

export interface LogActivityDto {
  deviceId: string;
  userId?: string;
  activityType: ActivityType;
  activityStatus: ActivityStatus;
  details?: Record<string, any>;
  ipAddress?: string;
  latitude?: number;
  longitude?: number;
}

export interface PaginationOptions {
  page: number;
  limit: number;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class DeviceService {
  private deviceRepository: Repository<Device>;
  private sessionRepository: Repository<DeviceSession>;
  private activityLogRepository: Repository<DeviceActivityLog>;

  constructor(dataSource: DataSource) {
    this.deviceRepository = dataSource.getRepository(Device);
    this.sessionRepository = dataSource.getRepository(DeviceSession);
    this.activityLogRepository = dataSource.getRepository(DeviceActivityLog);
  }

  // ============================================
  // DEVICE MANAGEMENT
  // ============================================

  /**
   * Register a new device or update existing
   */
  async registerDevice(tenantId: string, dto: RegisterDeviceDto): Promise<Device> {
    let device = await this.deviceRepository.findOne({
      where: { tenantId, userId: dto.userId, deviceUuid: dto.deviceUuid },
    });

    if (device) {
      // Update existing device
      device.deviceName = dto.deviceName || device.deviceName;
      device.deviceModel = dto.deviceModel || device.deviceModel;
      device.deviceBrand = dto.deviceBrand || device.deviceBrand;
      device.platformVersion = dto.platformVersion || device.platformVersion;
      device.appVersion = dto.appVersion || device.appVersion;
      device.pushToken = dto.pushToken || device.pushToken;
      device.pushTokenUpdatedAt = dto.pushToken ? new Date() : device.pushTokenUpdatedAt;
      device.lastIpAddress = dto.ipAddress || device.lastIpAddress;
      device.lastUserAgent = dto.userAgent || device.lastUserAgent;
      device.lastLatitude = dto.latitude ?? device.lastLatitude;
      device.lastLongitude = dto.longitude ?? device.lastLongitude;
      device.lastLocationAt = (dto.latitude || dto.longitude) ? new Date() : device.lastLocationAt;
      device.lastSeenAt = new Date();
      device.isActive = true;
    } else {
      // Create new device
      device = this.deviceRepository.create({
        tenantId,
        userId: dto.userId,
        deviceUuid: dto.deviceUuid,
        deviceName: dto.deviceName,
        deviceModel: dto.deviceModel,
        deviceBrand: dto.deviceBrand,
        platform: dto.platform,
        platformVersion: dto.platformVersion,
        appVersion: dto.appVersion,
        pushToken: dto.pushToken,
        pushTokenUpdatedAt: dto.pushToken ? new Date() : undefined,
        lastIpAddress: dto.ipAddress,
        lastUserAgent: dto.userAgent,
        lastLatitude: dto.latitude,
        lastLongitude: dto.longitude,
        lastLocationAt: (dto.latitude || dto.longitude) ? new Date() : undefined,
        firstSeenAt: new Date(),
        lastSeenAt: new Date(),
        isActive: true,
        isTrusted: false,
        trustLevel: 0,
        biometricEnabled: false,
      });
    }

    return this.deviceRepository.save(device);
  }

  /**
   * Find device by ID
   */
  async findById(tenantId: string, id: string): Promise<Device | null> {
    return this.deviceRepository.findOne({
      where: { id, tenantId },
      relations: ['biometricCredentials', 'sessions'],
    });
  }

  /**
   * Find device by UUID
   */
  async findByUuid(tenantId: string, userId: string, deviceUuid: string): Promise<Device | null> {
    return this.deviceRepository.findOne({
      where: { tenantId, userId, deviceUuid },
    });
  }

  /**
   * Find all devices for a user
   */
  async findByUser(
    tenantId: string,
    userId: string,
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<Device>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.deviceRepository.findAndCount({
      where: { tenantId, userId, deletedAt: IsNull() },
      order: { lastSeenAt: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * List all devices with filters
   */
  async findAll(
    tenantId: string,
    filters: DeviceFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<Device>> {
    const queryBuilder = this.deviceRepository.createQueryBuilder('device')
      .where('device.tenant_id = :tenantId', { tenantId })
      .andWhere('device.deleted_at IS NULL');

    if (filters.userId) {
      queryBuilder.andWhere('device.user_id = :userId', { userId: filters.userId });
    }
    if (filters.platform) {
      queryBuilder.andWhere('device.platform = :platform', { platform: filters.platform });
    }
    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('device.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.isTrusted !== undefined) {
      queryBuilder.andWhere('device.is_trusted = :isTrusted', { isTrusted: filters.isTrusted });
    }
    if (filters.biometricEnabled !== undefined) {
      queryBuilder.andWhere('device.biometric_enabled = :biometricEnabled', { biometricEnabled: filters.biometricEnabled });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(device.device_name ILIKE :search OR device.device_model ILIKE :search OR device.device_uuid ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .orderBy('device.last_seen_at', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Update device
   */
  async update(tenantId: string, id: string, dto: UpdateDeviceDto): Promise<Device | null> {
    const device = await this.findById(tenantId, id);
    if (!device) return null;

    if (dto.pushToken) {
      dto.pushToken = dto.pushToken;
      (device as any).pushTokenUpdatedAt = new Date();
    }

    Object.assign(device, dto);
    device.lastSeenAt = new Date();

    return this.deviceRepository.save(device);
  }

  /**
   * Update device location
   */
  async updateLocation(
    tenantId: string,
    id: string,
    latitude: number,
    longitude: number,
    ipAddress?: string
  ): Promise<Device | null> {
    const device = await this.findById(tenantId, id);
    if (!device) return null;

    device.lastLatitude = latitude;
    device.lastLongitude = longitude;
    device.lastLocationAt = new Date();
    device.lastSeenAt = new Date();
    if (ipAddress) {
      device.lastIpAddress = ipAddress;
    }

    return this.deviceRepository.save(device);
  }

  /**
   * Enable/disable biometric authentication
   */
  async toggleBiometric(
    tenantId: string,
    id: string,
    enabled: boolean,
    biometricType?: BiometricType
  ): Promise<Device | null> {
    const device = await this.findById(tenantId, id);
    if (!device) return null;

    device.biometricEnabled = enabled;
    if (enabled && biometricType) {
      device.biometricType = biometricType;
    } else if (!enabled) {
      device.biometricType = undefined as any;
    }

    return this.deviceRepository.save(device);
  }

  /**
   * Trust/untrust a device
   */
  async setTrust(tenantId: string, id: string, isTrusted: boolean, trustLevel?: number): Promise<Device | null> {
    const device = await this.findById(tenantId, id);
    if (!device) return null;

    device.isTrusted = isTrusted;
    if (trustLevel !== undefined) {
      device.trustLevel = trustLevel;
    } else if (isTrusted) {
      device.trustLevel = 2; // medium by default
    } else {
      device.trustLevel = 0;
    }

    return this.deviceRepository.save(device);
  }

  /**
   * Deactivate device
   */
  async deactivate(tenantId: string, id: string): Promise<boolean> {
    const result = await this.deviceRepository.update(
      { id, tenantId },
      { isActive: false }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Soft delete device
   */
  async delete(tenantId: string, id: string): Promise<boolean> {
    // Revoke all sessions first
    await this.revokeAllSessions(tenantId, id, 'device_deleted');

    const result = await this.deviceRepository.update(
      { id, tenantId },
      { deletedAt: new Date(), isActive: false }
    );
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // SESSION MANAGEMENT
  // ============================================

  /**
   * Create a new session
   */
  async createSession(tenantId: string, dto: CreateSessionDto): Promise<DeviceSession> {
    const session = this.sessionRepository.create({
      tenantId,
      deviceId: dto.deviceId,
      userId: dto.userId,
      accessTokenHash: dto.accessTokenHash,
      refreshTokenHash: dto.refreshTokenHash,
      authMethod: dto.authMethod,
      expiresAt: dto.expiresAt,
      refreshExpiresAt: dto.refreshExpiresAt,
      ipAddress: dto.ipAddress,
      userAgent: dto.userAgent,
      latitude: dto.latitude,
      longitude: dto.longitude,
      issuedAt: new Date(),
      isActive: true,
    });

    return this.sessionRepository.save(session);
  }

  /**
   * Find session by access token hash
   */
  async findSessionByToken(tenantId: string, accessTokenHash: string): Promise<DeviceSession | null> {
    return this.sessionRepository.findOne({
      where: {
        tenantId,
        accessTokenHash,
        isActive: true,
        expiresAt: MoreThan(new Date()),
      },
      relations: ['device'],
    });
  }

  /**
   * Get active sessions for device
   */
  async getActiveSessions(tenantId: string, deviceId: string): Promise<DeviceSession[]> {
    return this.sessionRepository.find({
      where: {
        tenantId,
        deviceId,
        isActive: true,
        expiresAt: MoreThan(new Date()),
      },
      order: { issuedAt: 'DESC' },
    });
  }

  /**
   * Get all sessions for user
   */
  async getUserSessions(
    tenantId: string,
    userId: string,
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<DeviceSession>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.sessionRepository.findAndCount({
      where: { tenantId, userId },
      relations: ['device'],
      order: { issuedAt: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Revoke session
   */
  async revokeSession(tenantId: string, sessionId: string, reason?: string): Promise<boolean> {
    const result = await this.sessionRepository.update(
      { id: sessionId, tenantId },
      {
        isActive: false,
        revokedAt: new Date(),
        revokedReason: reason || 'user_revoked',
      }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Revoke all sessions for device
   */
  async revokeAllSessions(tenantId: string, deviceId: string, reason?: string): Promise<number> {
    const result = await this.sessionRepository.update(
      { tenantId, deviceId, isActive: true },
      {
        isActive: false,
        revokedAt: new Date(),
        revokedReason: reason || 'all_revoked',
      }
    );
    return result.affected ?? 0;
  }

  /**
   * Revoke all user sessions across all devices
   */
  async revokeAllUserSessions(tenantId: string, userId: string, reason?: string): Promise<number> {
    const result = await this.sessionRepository.update(
      { tenantId, userId, isActive: true },
      {
        isActive: false,
        revokedAt: new Date(),
        revokedReason: reason || 'logout_all',
      }
    );
    return result.affected ?? 0;
  }

  /**
   * Cleanup expired sessions
   */
  async cleanupExpiredSessions(): Promise<number> {
    const result = await this.sessionRepository.update(
      { isActive: true, expiresAt: LessThan(new Date()) },
      { isActive: false, revokedReason: 'expired' }
    );
    return result.affected ?? 0;
  }

  // ============================================
  // ACTIVITY LOGGING
  // ============================================

  /**
   * Log device activity
   */
  async logActivity(dto: LogActivityDto): Promise<DeviceActivityLog> {
    const log = this.activityLogRepository.create({
      deviceId: dto.deviceId,
      userId: dto.userId,
      activityType: dto.activityType,
      activityStatus: dto.activityStatus,
      details: dto.details || {},
      ipAddress: dto.ipAddress,
      latitude: dto.latitude,
      longitude: dto.longitude,
    });

    return this.activityLogRepository.save(log);
  }

  /**
   * Get activity log for device
   */
  async getDeviceActivity(
    deviceId: string,
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<DeviceActivityLog>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.activityLogRepository.findAndCount({
      where: { deviceId },
      order: { createdAt: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Get activity log for user
   */
  async getUserActivity(
    userId: string,
    pagination: PaginationOptions = { page: 1, limit: 50 }
  ): Promise<PaginatedResult<DeviceActivityLog>> {
    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await this.activityLogRepository.findAndCount({
      where: { userId },
      order: { createdAt: 'DESC' },
      skip,
      take: pagination.limit,
    });

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  // ============================================
  // STATISTICS
  // ============================================

  /**
   * Get device statistics
   */
  async getStatistics(tenantId: string): Promise<{
    totalDevices: number;
    activeDevices: number;
    trustedDevices: number;
    biometricEnabled: number;
    byPlatform: Record<string, number>;
    activeSessions: number;
    recentLogins: number;
  }> {
    const now = new Date();
    const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000);

    const [
      totalDevices,
      activeDevices,
      trustedDevices,
      biometricEnabled,
      byPlatformRaw,
      activeSessions,
      recentLogins,
    ] = await Promise.all([
      this.deviceRepository.count({ where: { tenantId, deletedAt: IsNull() } }),
      this.deviceRepository.count({ where: { tenantId, isActive: true, deletedAt: IsNull() } }),
      this.deviceRepository.count({ where: { tenantId, isTrusted: true, deletedAt: IsNull() } }),
      this.deviceRepository.count({ where: { tenantId, biometricEnabled: true, deletedAt: IsNull() } }),

      this.deviceRepository.createQueryBuilder('device')
        .select('device.platform', 'platform')
        .addSelect('COUNT(*)', 'count')
        .where('device.tenant_id = :tenantId', { tenantId })
        .andWhere('device.deleted_at IS NULL')
        .groupBy('device.platform')
        .getRawMany(),

      this.sessionRepository.count({
        where: {
          tenantId,
          isActive: true,
          expiresAt: MoreThan(now),
        },
      }),

      this.activityLogRepository.createQueryBuilder('log')
        .innerJoin('auth.devices', 'device', 'device.id = log.device_id')
        .where('device.tenant_id = :tenantId', { tenantId })
        .andWhere('log.activity_type = :type', { type: 'login' })
        .andWhere('log.activity_status = :status', { status: 'success' })
        .andWhere('log.created_at >= :since', { since: last24Hours })
        .getCount(),
    ]);

    const byPlatform: Record<string, number> = {};
    byPlatformRaw.forEach((row: any) => {
      byPlatform[row.platform] = parseInt(row.count, 10);
    });

    return {
      totalDevices,
      activeDevices,
      trustedDevices,
      biometricEnabled,
      byPlatform,
      activeSessions,
      recentLogins,
    };
  }
}
