/**
 * Mobile Session Service
 *
 * Service for managing mobile app sessions with offline mode and location tracking
 *
 * @module Mobile
 */

import { Repository, DataSource, LessThan } from 'typeorm';
import { MobileSession } from '../entities/mobile-session.entity';
import {
  CreateMobileSessionDto,
  UpdateMobileSessionDto,
  UpdateLocationDto,
  MobileSessionResponseDto,
  MobileSessionFilterDto,
} from '../dto/mobile-session.dto';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export class MobileSessionService {
  private sessionRepository: Repository<MobileSession>;

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

  /**
   * Create a new mobile session
   */
  async create(ctx: ServiceContext, dto: CreateMobileSessionDto): Promise<MobileSession> {
    // Terminate any existing active sessions for this device
    await this.terminateDeviceSessions(dto.deviceId);

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

    return this.sessionRepository.save(session);
  }

  /**
   * Find session by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<MobileSession | null> {
    return this.sessionRepository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  /**
   * Find active session for device
   */
  async findActiveByDevice(ctx: ServiceContext, deviceId: string): Promise<MobileSession | null> {
    return this.sessionRepository.findOne({
      where: {
        deviceId,
        tenantId: ctx.tenantId,
        status: 'active',
      },
      order: { startedAt: 'DESC' },
    });
  }

  /**
   * Find sessions by user
   */
  async findByUser(ctx: ServiceContext, userId: string): Promise<MobileSession[]> {
    return this.sessionRepository.find({
      where: { userId, tenantId: ctx.tenantId },
      order: { startedAt: 'DESC' },
    });
  }

  /**
   * Find sessions with filters
   */
  async findAll(
    ctx: ServiceContext,
    filter: MobileSessionFilterDto
  ): Promise<{ data: MobileSession[]; total: number }> {
    const query = this.sessionRepository
      .createQueryBuilder('session')
      .where('session.tenantId = :tenantId', { tenantId: ctx.tenantId });

    if (filter.userId) {
      query.andWhere('session.userId = :userId', { userId: filter.userId });
    }

    if (filter.deviceId) {
      query.andWhere('session.deviceId = :deviceId', { deviceId: filter.deviceId });
    }

    if (filter.branchId) {
      query.andWhere('session.branchId = :branchId', { branchId: filter.branchId });
    }

    if (filter.status) {
      query.andWhere('session.status = :status', { status: filter.status });
    }

    if (filter.isOfflineMode !== undefined) {
      query.andWhere('session.isOfflineMode = :isOfflineMode', { isOfflineMode: filter.isOfflineMode });
    }

    const total = await query.getCount();

    query.orderBy('session.lastActivityAt', 'DESC');

    if (filter.limit) {
      query.take(filter.limit);
    }

    if (filter.offset) {
      query.skip(filter.offset);
    }

    const data = await query.getMany();

    return { data, total };
  }

  /**
   * Update session
   */
  async update(ctx: ServiceContext, id: string, dto: UpdateMobileSessionDto): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    // Handle offline mode transition
    if (dto.isOfflineMode !== undefined && dto.isOfflineMode !== session.isOfflineMode) {
      if (dto.isOfflineMode) {
        session.offlineSince = new Date();
      } else {
        session.offlineSince = undefined as any;
      }
    }

    Object.assign(session, dto);
    session.lastActivityAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Update session location
   */
  async updateLocation(ctx: ServiceContext, id: string, dto: UpdateLocationDto): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    session.lastLatitude = dto.latitude;
    session.lastLongitude = dto.longitude;
    session.lastLocationAt = new Date();
    session.lastActivityAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Record activity (heartbeat)
   */
  async recordActivity(ctx: ServiceContext, id: string): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    session.lastActivityAt = new Date();
    session.expiresAt = this.calculateExpirationDate();

    return this.sessionRepository.save(session);
  }

  /**
   * Set offline mode
   */
  async setOfflineMode(ctx: ServiceContext, id: string, offline: boolean): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    session.isOfflineMode = offline;
    if (offline) {
      session.offlineSince = new Date();
    } else {
      session.offlineSince = undefined as any;
      session.lastSyncAt = new Date();
    }
    session.lastActivityAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Update sync status
   */
  async updateSyncStatus(ctx: ServiceContext, id: string, pendingCount: number): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    session.pendingSyncCount = pendingCount;
    session.lastSyncAt = new Date();
    session.lastActivityAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Terminate session
   */
  async terminate(ctx: ServiceContext, id: string): Promise<MobileSession> {
    const session = await this.findById(ctx, id);
    if (!session) {
      throw new Error('Session not found');
    }

    session.status = 'terminated';
    session.endedAt = new Date();

    return this.sessionRepository.save(session);
  }

  /**
   * Terminate all sessions for a device
   */
  async terminateDeviceSessions(deviceId: string): Promise<number> {
    const result = await this.sessionRepository.update(
      { deviceId, status: 'active' },
      { status: 'terminated', endedAt: new Date() }
    );

    return result.affected || 0;
  }

  /**
   * Expire old sessions
   */
  async expireOldSessions(): Promise<number> {
    const result = await this.sessionRepository.update(
      {
        status: 'active',
        expiresAt: LessThan(new Date()),
      },
      { status: 'expired' }
    );

    return result.affected || 0;
  }

  /**
   * Get active sessions count by tenant
   */
  async getActiveSessionsCount(ctx: ServiceContext): Promise<number> {
    return this.sessionRepository.count({
      where: { tenantId: ctx.tenantId, status: 'active' },
    });
  }

  /**
   * Get sessions in offline mode
   */
  async getOfflineSessions(ctx: ServiceContext): Promise<MobileSession[]> {
    return this.sessionRepository.find({
      where: { tenantId: ctx.tenantId, status: 'active', isOfflineMode: true },
      order: { offlineSince: 'ASC' },
    });
  }

  /**
   * Convert entity to response DTO
   */
  toResponseDto(session: MobileSession): MobileSessionResponseDto {
    return {
      id: session.id,
      userId: session.userId,
      deviceId: session.deviceId,
      tenantId: session.tenantId,
      branchId: session.branchId,
      status: session.status,
      activeProfileId: session.activeProfileId,
      activeProfileCode: session.activeProfileCode,
      isOfflineMode: session.isOfflineMode,
      offlineSince: session.offlineSince,
      lastSyncAt: session.lastSyncAt,
      pendingSyncCount: session.pendingSyncCount,
      lastLatitude: session.lastLatitude,
      lastLongitude: session.lastLongitude,
      lastLocationAt: session.lastLocationAt,
      appVersion: session.appVersion,
      platform: session.platform,
      osVersion: session.osVersion,
      startedAt: session.startedAt,
      lastActivityAt: session.lastActivityAt,
      expiresAt: session.expiresAt,
    };
  }

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