/**
 * LoginHistory Service
 * Authentication event tracking with device, location and risk scoring.
 *
 * @module Audit
 */

import { Repository } from 'typeorm';
import {
  LoginHistory,
  LoginStatus,
  AuthMethod,
  MfaMethod,
} from '../entities/login-history.entity';
import { ServiceContext, PaginatedResult } from './audit-log.service';

export interface CreateLoginHistoryDto {
  userId?: string;
  email?: string;
  username?: string;
  status: LoginStatus;
  authMethod?: AuthMethod;
  oauthProvider?: string;
  mfaMethod?: MfaMethod;
  mfaVerified?: boolean;
  deviceId?: string;
  deviceFingerprint?: string;
  deviceType?: string;
  deviceOs?: string;
  deviceBrowser?: string;
  ipAddress?: string;
  userAgent?: string;
  countryCode?: string;
  city?: string;
  latitude?: number;
  longitude?: number;
  riskScore?: number;
  riskFactors?: string[];
  isSuspicious?: boolean;
  isNewDevice?: boolean;
  isNewLocation?: boolean;
  failureReason?: string;
  failureCount?: number;
}

export interface LoginHistoryFilters {
  userId?: string;
  email?: string;
  status?: LoginStatus;
  authMethod?: AuthMethod;
  ipAddress?: string;
  isSuspicious?: boolean;
  isNewDevice?: boolean;
  isNewLocation?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
  minRiskScore?: number;
}

export interface LoginStats {
  period: { days: number; from: Date; to: Date };
  totalLogins: number;
  successfulLogins: number;
  failedLogins: number;
  suspiciousLogins: number;
  newDeviceLogins: number;
  newLocationLogins: number;
  byAuthMethod: { method: string; count: number }[];
  byStatus: { status: string; count: number }[];
  averageRiskScore: number;
}

export class LoginHistoryService {
  constructor(private readonly repository: Repository<LoginHistory>) {}

  /**
   * Record a login attempt
   */
  async create(
    ctx: ServiceContext,
    dto: CreateLoginHistoryDto,
  ): Promise<LoginHistory> {
    const login = this.repository.create({
      tenantId: ctx.tenantId,
      userId: dto.userId,
      email: dto.email,
      username: dto.username,
      status: dto.status,
      authMethod: dto.authMethod,
      oauthProvider: dto.oauthProvider,
      mfaMethod: dto.mfaMethod,
      mfaVerified: dto.mfaVerified,
      deviceId: dto.deviceId,
      deviceFingerprint: dto.deviceFingerprint,
      deviceType: dto.deviceType,
      deviceOs: dto.deviceOs,
      deviceBrowser: dto.deviceBrowser,
      ipAddress: dto.ipAddress,
      userAgent: dto.userAgent,
      countryCode: dto.countryCode,
      city: dto.city,
      latitude: dto.latitude,
      longitude: dto.longitude,
      riskScore: dto.riskScore,
      riskFactors: dto.riskFactors || [],
      isSuspicious: dto.isSuspicious || false,
      isNewDevice: dto.isNewDevice || false,
      isNewLocation: dto.isNewLocation || false,
      failureReason: dto.failureReason,
      failureCount: dto.failureCount,
    });

    return this.repository.save(login);
  }

  /**
   * Find login history with filters
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: LoginHistoryFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<LoginHistory>> {
    const qb = this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('lh.user_id = :userId', { userId: filters.userId });
    }
    if (filters.email) {
      qb.andWhere('lh.email = :email', { email: filters.email });
    }
    if (filters.status) {
      qb.andWhere('lh.status = :status', { status: filters.status });
    }
    if (filters.authMethod) {
      qb.andWhere('lh.auth_method = :authMethod', { authMethod: filters.authMethod });
    }
    if (filters.ipAddress) {
      qb.andWhere('lh.ip_address = :ipAddress', { ipAddress: filters.ipAddress });
    }
    if (filters.isSuspicious !== undefined) {
      qb.andWhere('lh.is_suspicious = :isSuspicious', { isSuspicious: filters.isSuspicious });
    }
    if (filters.isNewDevice !== undefined) {
      qb.andWhere('lh.is_new_device = :isNewDevice', { isNewDevice: filters.isNewDevice });
    }
    if (filters.isNewLocation !== undefined) {
      qb.andWhere('lh.is_new_location = :isNewLocation', { isNewLocation: filters.isNewLocation });
    }
    if (filters.dateFrom) {
      qb.andWhere('lh.attempted_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('lh.attempted_at <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.minRiskScore !== undefined) {
      qb.andWhere('lh.risk_score >= :minRiskScore', { minRiskScore: filters.minRiskScore });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('lh.attempted_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

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

  /**
   * Get login history for a user
   */
  async findByUser(
    ctx: ServiceContext,
    userId: string,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<LoginHistory>> {
    return this.findWithFilters(ctx, { userId }, page, limit);
  }

  /**
   * Get suspicious login attempts
   */
  async getSuspiciousLogins(
    ctx: ServiceContext,
    days = 7,
    limit = 100,
  ): Promise<LoginHistory[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.is_suspicious = true')
      .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
      .orderBy('lh.attempted_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get failed login attempts
   */
  async getFailedLogins(
    ctx: ServiceContext,
    hours = 24,
    limit = 100,
  ): Promise<LoginHistory[]> {
    const dateFrom = new Date();
    dateFrom.setHours(dateFrom.getHours() - hours);

    return this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.status = :status', { status: 'failed' })
      .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
      .orderBy('lh.attempted_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get high risk logins
   */
  async getHighRiskLogins(
    ctx: ServiceContext,
    minRiskScore = 70,
    days = 7,
    limit = 100,
  ): Promise<LoginHistory[]> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    return this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.risk_score >= :minRiskScore', { minRiskScore })
      .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
      .orderBy('lh.risk_score', 'DESC')
      .addOrderBy('lh.attempted_at', 'DESC')
      .take(limit)
      .getMany();
  }

  /**
   * Get login statistics
   */
  async getStats(ctx: ServiceContext, days = 30): Promise<LoginStats> {
    const dateFrom = new Date();
    dateFrom.setDate(dateFrom.getDate() - days);

    const baseQuery = this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.attempted_at >= :dateFrom', { dateFrom });

    const [
      totalLogins,
      successfulLogins,
      failedLogins,
      suspiciousLogins,
      newDeviceLogins,
      newLocationLogins,
      byAuthMethod,
      byStatus,
      avgRiskScore,
    ] = await Promise.all([
      baseQuery.clone().getCount(),
      baseQuery.clone().andWhere('lh.status = :status', { status: 'success' }).getCount(),
      baseQuery.clone().andWhere('lh.status = :status', { status: 'failed' }).getCount(),
      baseQuery.clone().andWhere('lh.is_suspicious = true').getCount(),
      baseQuery.clone().andWhere('lh.is_new_device = true').getCount(),
      baseQuery.clone().andWhere('lh.is_new_location = true').getCount(),
      this.repository
        .createQueryBuilder('lh')
        .select('lh.auth_method', 'method')
        .addSelect('COUNT(*)', 'count')
        .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
        .groupBy('lh.auth_method')
        .getRawMany(),
      this.repository
        .createQueryBuilder('lh')
        .select('lh.status', 'status')
        .addSelect('COUNT(*)', 'count')
        .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
        .groupBy('lh.status')
        .getRawMany(),
      this.repository
        .createQueryBuilder('lh')
        .select('AVG(lh.risk_score)', 'avg')
        .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('lh.attempted_at >= :dateFrom', { dateFrom })
        .andWhere('lh.risk_score IS NOT NULL')
        .getRawOne(),
    ]);

    return {
      period: { days, from: dateFrom, to: new Date() },
      totalLogins,
      successfulLogins,
      failedLogins,
      suspiciousLogins,
      newDeviceLogins,
      newLocationLogins,
      byAuthMethod: byAuthMethod.map((r) => ({
        method: r.method || 'unknown',
        count: parseInt(r.count),
      })),
      byStatus: byStatus.map((r) => ({ status: r.status, count: parseInt(r.count) })),
      averageRiskScore: avgRiskScore?.avg ? parseFloat(avgRiskScore.avg) : 0,
    };
  }

  /**
   * Check if device is known for user
   */
  async isKnownDevice(
    ctx: ServiceContext,
    userId: string,
    deviceFingerprint: string,
  ): Promise<boolean> {
    const count = await this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.user_id = :userId', { userId })
      .andWhere('lh.device_fingerprint = :fingerprint', { fingerprint: deviceFingerprint })
      .andWhere('lh.status = :status', { status: 'success' })
      .getCount();

    return count > 0;
  }

  /**
   * Check if location is known for user
   */
  async isKnownLocation(
    ctx: ServiceContext,
    userId: string,
    countryCode: string,
    city?: string,
  ): Promise<boolean> {
    const qb = this.repository
      .createQueryBuilder('lh')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.user_id = :userId', { userId })
      .andWhere('lh.country_code = :countryCode', { countryCode })
      .andWhere('lh.status = :status', { status: 'success' });

    if (city) {
      qb.andWhere('lh.city = :city', { city });
    }

    const count = await qb.getCount();
    return count > 0;
  }

  /**
   * Get user's known devices
   */
  async getUserDevices(
    ctx: ServiceContext,
    userId: string,
  ): Promise<{ deviceFingerprint: string; deviceType: string; lastUsed: Date }[]> {
    const devices = await this.repository
      .createQueryBuilder('lh')
      .select('lh.device_fingerprint', 'deviceFingerprint')
      .addSelect('lh.device_type', 'deviceType')
      .addSelect('MAX(lh.attempted_at)', 'lastUsed')
      .where('lh.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('lh.user_id = :userId', { userId })
      .andWhere('lh.device_fingerprint IS NOT NULL')
      .andWhere('lh.status = :status', { status: 'success' })
      .groupBy('lh.device_fingerprint')
      .addGroupBy('lh.device_type')
      .orderBy('MAX(lh.attempted_at)', 'DESC')
      .getRawMany();

    return devices;
  }
}
