import { Repository, IsNull, LessThan } from 'typeorm';
import { TrustedDevice, TrustLevel } from '../entities';

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

export interface CreateTrustedDeviceDto {
  userId: string;
  deviceId: string;
  trustLevel?: TrustLevel;
  expiresAt?: Date;
  skipMfa?: boolean;
}

export interface UpdateTrustedDeviceDto {
  trustLevel?: TrustLevel;
  expiresAt?: Date;
  skipMfa?: boolean;
  isRevoked?: boolean;
}

export interface TrustedDeviceFilters {
  userId?: string;
  deviceId?: string;
  trustLevel?: TrustLevel;
  isRevoked?: boolean;
  isExpired?: boolean;
}

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

export class TrustedDeviceService {
  constructor(private readonly trustedDeviceRepository: Repository<TrustedDevice>) {}

  async create(ctx: ServiceContext, data: CreateTrustedDeviceDto): Promise<TrustedDevice> {
    const existing = await this.findByUserAndDevice(ctx, data.userId, data.deviceId);
    if (existing && !existing.isRevoked) {
      return this.renew(ctx, existing.id) as Promise<TrustedDevice>;
    }

    const defaultExpiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

    const trustedDevice = this.trustedDeviceRepository.create({
      tenantId: ctx.tenantId,
      userId: data.userId,
      deviceId: data.deviceId,
      trustLevel: data.trustLevel ?? TrustLevel.STANDARD,
      expiresAt: data.expiresAt ?? defaultExpiry,
      skipMfa: data.skipMfa ?? false,
      trustedAt: new Date(),
      createdBy: ctx.userId ?? null,
    });

    return this.trustedDeviceRepository.save(trustedDevice);
  }

  async findById(ctx: ServiceContext, id: string): Promise<TrustedDevice | null> {
    return this.trustedDeviceRepository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['user', 'device'],
    });
  }

  async findByUserAndDevice(ctx: ServiceContext, userId: string, deviceId: string): Promise<TrustedDevice | null> {
    return this.trustedDeviceRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        userId,
        deviceId,
      },
      relations: ['device'],
    });
  }

  async findByUserId(ctx: ServiceContext, userId: string): Promise<TrustedDevice[]> {
    return this.trustedDeviceRepository.find({
      where: { tenantId: ctx.tenantId, userId, isRevoked: false },
      relations: ['device'],
      order: { trustedAt: 'DESC' },
    });
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: TrustedDeviceFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<TrustedDevice>> {
    const qb = this.trustedDeviceRepository
      .createQueryBuilder('td')
      .leftJoinAndSelect('td.user', 'user')
      .leftJoinAndSelect('td.device', 'device')
      .where('td.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.userId) {
      qb.andWhere('td.user_id = :userId', { userId: filters.userId });
    }

    if (filters.deviceId) {
      qb.andWhere('td.device_id = :deviceId', { deviceId: filters.deviceId });
    }

    if (filters.trustLevel) {
      qb.andWhere('td.trust_level = :trustLevel', { trustLevel: filters.trustLevel });
    }

    if (filters.isRevoked !== undefined) {
      qb.andWhere('td.is_revoked = :isRevoked', { isRevoked: filters.isRevoked });
    }

    if (filters.isExpired !== undefined) {
      if (filters.isExpired) {
        qb.andWhere('td.expires_at < :now', { now: new Date() });
      } else {
        qb.andWhere('(td.expires_at IS NULL OR td.expires_at >= :now)', { now: new Date() });
      }
    }

    const [data, total] = await qb
      .orderBy('td.trusted_at', 'DESC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

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

  async update(ctx: ServiceContext, id: string, data: UpdateTrustedDeviceDto): Promise<TrustedDevice | null> {
    const trustedDevice = await this.findById(ctx, id);
    if (!trustedDevice) return null;

    Object.assign(trustedDevice, {
      ...data,
      updatedBy: ctx.userId ?? null,
    });

    return this.trustedDeviceRepository.save(trustedDevice);
  }

  async renew(ctx: ServiceContext, id: string, daysToAdd = 30): Promise<TrustedDevice | null> {
    const trustedDevice = await this.findById(ctx, id);
    if (!trustedDevice || trustedDevice.isRevoked) return null;

    trustedDevice.expiresAt = new Date(Date.now() + daysToAdd * 24 * 60 * 60 * 1000);
    trustedDevice.updatedBy = ctx.userId ?? null;

    return this.trustedDeviceRepository.save(trustedDevice);
  }

  async revoke(ctx: ServiceContext, id: string): Promise<boolean> {
    const trustedDevice = await this.findById(ctx, id);
    if (!trustedDevice) return false;

    trustedDevice.isRevoked = true;
    trustedDevice.revokedAt = new Date();
    trustedDevice.updatedBy = ctx.userId ?? null;
    await this.trustedDeviceRepository.save(trustedDevice);

    return true;
  }

  async revokeAllUserDevices(ctx: ServiceContext, userId: string): Promise<number> {
    const devices = await this.findByUserId(ctx, userId);
    let count = 0;

    for (const device of devices) {
      device.isRevoked = true;
      device.revokedAt = new Date();
      device.updatedBy = ctx.userId ?? null;
      await this.trustedDeviceRepository.save(device);
      count++;
    }

    return count;
  }

  async isTrusted(ctx: ServiceContext, userId: string, deviceId: string): Promise<boolean> {
    const trustedDevice = await this.findByUserAndDevice(ctx, userId, deviceId);
    if (!trustedDevice) return false;
    if (trustedDevice.isRevoked) return false;
    if (trustedDevice.expiresAt && trustedDevice.expiresAt < new Date()) return false;

    return true;
  }

  async shouldSkipMfa(ctx: ServiceContext, userId: string, deviceId: string): Promise<boolean> {
    const trustedDevice = await this.findByUserAndDevice(ctx, userId, deviceId);
    if (!trustedDevice) return false;
    if (trustedDevice.isRevoked) return false;
    if (trustedDevice.expiresAt && trustedDevice.expiresAt < new Date()) return false;

    return trustedDevice.skipMfa;
  }

  async getTrustLevel(ctx: ServiceContext, userId: string, deviceId: string): Promise<TrustLevel | null> {
    const trustedDevice = await this.findByUserAndDevice(ctx, userId, deviceId);
    if (!trustedDevice || trustedDevice.isRevoked) return null;
    if (trustedDevice.expiresAt && trustedDevice.expiresAt < new Date()) return null;

    return trustedDevice.trustLevel;
  }

  async cleanupExpired(ctx: ServiceContext): Promise<number> {
    const result = await this.trustedDeviceRepository.update(
      {
        tenantId: ctx.tenantId,
        isRevoked: false,
        expiresAt: LessThan(new Date()),
      },
      {
        isRevoked: true,
        revokedAt: new Date(),
      },
    );

    return result.affected ?? 0;
  }

  async trustDevice(
    ctx: ServiceContext,
    userId: string,
    deviceId: string,
    options?: { trustLevel?: TrustLevel; skipMfa?: boolean; days?: number },
  ): Promise<TrustedDevice> {
    const existing = await this.findByUserAndDevice(ctx, userId, deviceId);

    if (existing && !existing.isRevoked) {
      return this.renew(ctx, existing.id, options?.days ?? 30) as Promise<TrustedDevice>;
    }

    return this.create(ctx, {
      userId,
      deviceId,
      trustLevel: options?.trustLevel ?? TrustLevel.STANDARD,
      skipMfa: options?.skipMfa ?? false,
      expiresAt: new Date(Date.now() + (options?.days ?? 30) * 24 * 60 * 60 * 1000),
    });
  }
}
