import { Repository, IsNull } from 'typeorm';
import { Device } from '../entities';

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

export interface CreateDeviceDto {
  userId: string;
  fingerprint: string;
  name?: string;
  deviceType?: string;
  browser?: string;
  os?: string;
  ipAddress?: string;
  userAgent?: string;
}

export interface UpdateDeviceDto {
  name?: string;
  ipAddress?: string;
  userAgent?: string;
  isBlocked?: boolean;
}

export interface DeviceFilters {
  userId?: string;
  deviceType?: string;
  isBlocked?: boolean;
  search?: string;
}

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

export class DeviceService {
  constructor(private readonly deviceRepository: Repository<Device>) {}

  async create(ctx: ServiceContext, data: CreateDeviceDto): Promise<Device> {
    const existing = await this.findByFingerprint(ctx, data.userId, data.fingerprint);
    if (existing) {
      return this.updateLastSeen(ctx, existing.id) as Promise<Device>;
    }

    const device = this.deviceRepository.create({
      tenantId: ctx.tenantId,
      userId: data.userId,
      fingerprint: data.fingerprint,
      name: data.name || this.generateDeviceName(data),
      deviceType: data.deviceType,
      browser: data.browser,
      os: data.os,
      ipAddress: data.ipAddress,
      userAgent: data.userAgent,
      lastSeenAt: new Date(),
      createdBy: ctx.userId ?? null,
    });

    return this.deviceRepository.save(device);
  }

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

  async findByFingerprint(ctx: ServiceContext, userId: string, fingerprint: string): Promise<Device | null> {
    return this.deviceRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        userId,
        fingerprint,
        deletedAt: IsNull(),
      },
    });
  }

  async findByUserId(ctx: ServiceContext, userId: string): Promise<Device[]> {
    return this.deviceRepository.find({
      where: { tenantId: ctx.tenantId, userId, deletedAt: IsNull() },
      order: { lastSeenAt: 'DESC' },
    });
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: DeviceFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<Device>> {
    const qb = this.deviceRepository
      .createQueryBuilder('device')
      .leftJoinAndSelect('device.user', 'user')
      .where('device.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('device.deleted_at IS NULL');

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

    if (filters.deviceType) {
      qb.andWhere('device.device_type = :deviceType', { deviceType: filters.deviceType });
    }

    if (filters.isBlocked !== undefined) {
      qb.andWhere('device.is_blocked = :isBlocked', { isBlocked: filters.isBlocked });
    }

    if (filters.search) {
      qb.andWhere('(device.name ILIKE :search OR device.fingerprint ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const [data, total] = await qb
      .orderBy('device.last_seen_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: UpdateDeviceDto): Promise<Device | null> {
    const device = await this.findById(ctx, id);
    if (!device) return null;

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

    return this.deviceRepository.save(device);
  }

  async updateLastSeen(ctx: ServiceContext, id: string): Promise<Device | null> {
    const device = await this.findById(ctx, id);
    if (!device) return null;

    device.lastSeenAt = new Date();
    return this.deviceRepository.save(device);
  }

  async block(ctx: ServiceContext, id: string): Promise<Device | null> {
    return this.update(ctx, id, { isBlocked: true });
  }

  async unblock(ctx: ServiceContext, id: string): Promise<Device | null> {
    return this.update(ctx, id, { isBlocked: false });
  }

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

    device.deletedAt = new Date();
    device.deletedBy = ctx.userId ?? null;
    await this.deviceRepository.save(device);

    return true;
  }

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

    for (const device of devices) {
      device.deletedAt = new Date();
      device.deletedBy = ctx.userId ?? null;
      await this.deviceRepository.save(device);
      count++;
    }

    return count;
  }

  async isDeviceBlocked(ctx: ServiceContext, userId: string, fingerprint: string): Promise<boolean> {
    const device = await this.findByFingerprint(ctx, userId, fingerprint);
    return device?.isBlocked ?? false;
  }

  async getDeviceCount(ctx: ServiceContext, userId: string): Promise<number> {
    return this.deviceRepository.count({
      where: { tenantId: ctx.tenantId, userId, deletedAt: IsNull() },
    });
  }

  async getOrCreate(ctx: ServiceContext, data: CreateDeviceDto): Promise<Device> {
    const existing = await this.findByFingerprint(ctx, data.userId, data.fingerprint);
    if (existing) {
      existing.lastSeenAt = new Date();
      existing.ipAddress = data.ipAddress || existing.ipAddress;
      existing.userAgent = data.userAgent || existing.userAgent;
      return this.deviceRepository.save(existing);
    }

    return this.create(ctx, data);
  }

  private generateDeviceName(data: CreateDeviceDto): string {
    const parts: string[] = [];
    if (data.browser) parts.push(data.browser);
    if (data.os) parts.push(`on ${data.os}`);
    if (data.deviceType) parts.push(`(${data.deviceType})`);

    return parts.length > 0 ? parts.join(' ') : 'Unknown Device';
  }
}
