import { In, type Repository } from 'typeorm';

import type { Driver } from '@core/racing/domain/entities/Driver';
import type { SocialGraphRepository } from '@core/social/domain/repositories/SocialGraphRepository';

import { DriverOrmEntity } from '@adapters/racing/persistence/typeorm/entities/DriverOrmEntity';
import { DriverOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/DriverOrmMapper';

import { FriendshipOrmEntity } from '../entities/FriendshipOrmEntity';
import { TypeOrmSocialSchemaError } from '../errors/TypeOrmSocialSchemaError';
import { assertNonEmptyString } from '../schema/TypeOrmSocialSchemaGuards';

export class TypeOrmSocialGraphRepository implements SocialGraphRepository {
  constructor(
    private readonly friendshipRepo: Repository<FriendshipOrmEntity>,
    private readonly driverRepo: Repository<DriverOrmEntity>,
    private readonly driverMapper: DriverOrmMapper,
  ) {}

  async getFriends(driverId: string): Promise<Driver[]> {
    const friendIds = await this.getFriendIds(driverId);
    if (friendIds.length === 0) return [];

    const drivers = await this.driverRepo.find({ where: { id: In(friendIds) } });
    const byId = new Map(drivers.map((d) => [d.id, d]));

    return friendIds
      .map((id) => byId.get(id))
      .filter((d): d is DriverOrmEntity => Boolean(d))
      .map((d) => this.driverMapper.toDomain(d));
  }

  async getFriendIds(driverId: string): Promise<string[]> {
    const entityName = 'SocialFriendship';

    const friendships = await this.friendshipRepo.find({ where: { driverId } });

    return friendships.map((f) => {
      try {
        assertNonEmptyString(entityName, 'driverId', f.driverId);
        assertNonEmptyString(entityName, 'friendId', f.friendId);
        return f.friendId;
      } catch (error) {
        if (error instanceof TypeOrmSocialSchemaError) throw error;
        const message = error instanceof Error ? error.message : 'Invalid persisted SocialFriendship';
        throw new TypeOrmSocialSchemaError({ entityName, fieldName: 'unknown', reason: 'invalid_shape', message });
      }
    });
  }

  async getSuggestedFriends(driverId: string, limit?: number): Promise<Driver[]> {
    const directFriendIds = new Set(await this.getFriendIds(driverId));
    if (directFriendIds.size === 0) return [];

    const friendships = await this.friendshipRepo.find({
      where: { driverId: In(Array.from(directFriendIds)) },
    });

    const suggestions = new Map<string, number>();

    for (const friendship of friendships) {
      const friendOfFriendId = friendship.friendId;
      if (friendOfFriendId === driverId) continue;
      if (directFriendIds.has(friendOfFriendId)) continue;

      suggestions.set(friendOfFriendId, (suggestions.get(friendOfFriendId) ?? 0) + 1);
    }

    const rankedIds = Array.from(suggestions.entries())
      .sort((a, b) => b[1] - a[1])
      .map(([id]) => id);

    const topIds = typeof limit === 'number' ? rankedIds.slice(0, limit) : rankedIds;
    if (topIds.length === 0) return [];

    const drivers = await this.driverRepo.find({ where: { id: In(topIds) } });
    const byId = new Map(drivers.map((d) => [d.id, d]));

    return topIds
      .map((id) => byId.get(id))
      .filter((d): d is DriverOrmEntity => Boolean(d))
      .map((d) => this.driverMapper.toDomain(d));
  }
}