import type { Driver } from '@core/racing/domain/entities/Driver';
import type { Logger } from '@core/shared/application';
import type { IFeedRepository } from '@core/social/domain/repositories/IFeedRepository';
import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository';
import type { FeedItem } from '@core/social/domain/types/FeedItem';

export type Friendship = {
  driverId: string;
  friendId: string;
};

export type RacingSeedData = {
  drivers: Driver[];
  friendships: Friendship[];
  feedEvents: FeedItem[];
};

export class InMemoryFeedRepository implements IFeedRepository {
  private feedEvents: FeedItem[];
  private friendships: Friendship[];
  private readonly logger: Logger;

  constructor(logger: Logger, seed?: RacingSeedData) {
    this.logger = logger;
    this.logger.info('InMemoryFeedRepository initialized.');
    this.feedEvents = seed?.feedEvents ?? [];
    this.friendships = seed?.friendships ?? [];
  }

  seed(seed: RacingSeedData): void {
    this.feedEvents = seed.feedEvents;
    this.friendships = seed.friendships;
  }

  async getFeedForDriver(driverId: string, limit?: number): Promise<FeedItem[]> {
    this.logger.debug(`Getting feed for driver: ${driverId}, limit: ${limit}`);
    try {
      const friendIds = new Set(
        this.friendships
          .filter((f) => f.driverId === driverId)
          .map((f) => f.friendId),
      );

      const items = this.feedEvents.filter((item) => {
        if (item.actorDriverId && friendIds.has(item.actorDriverId)) {
          return true;
        }
        return false;
      });

      const sorted = items
        .slice()
        .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());

      this.logger.info(`Found ${sorted.length} feed items for driver: ${driverId}.`);
      return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
    } catch (error) {
      this.logger.error(`Error getting feed for driver ${driverId}:`, error as Error);
      throw error;
    }
  }

  async getGlobalFeed(limit?: number): Promise<FeedItem[]> {
    this.logger.debug(`Getting global feed, limit: ${limit}`);
    try {
      const sorted = this.feedEvents
        .slice()
        .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());

      this.logger.info(`Found ${sorted.length} global feed items.`);
      return typeof limit === 'number' ? sorted.slice(0, limit) : sorted;
    } catch (error) {
      this.logger.error(`Error getting global feed:`, error as Error);
      throw error;
    }
  }
}

export class InMemorySocialGraphRepository implements ISocialGraphRepository {
  private friendships: Friendship[];
  private driversById: Map<string, Driver>;
  private readonly logger: Logger;

  constructor(logger: Logger, seed?: RacingSeedData) {
    this.logger = logger;
    this.logger.info('InMemorySocialGraphRepository initialized.');
    this.friendships = seed?.friendships ?? [];
    this.driversById = new Map((seed?.drivers ?? []).map((d) => [d.id, d]));
  }

  seed(seed: RacingSeedData): void {
    this.friendships = seed.friendships;
    this.driversById = new Map(seed.drivers.map((d) => [d.id, d]));
  }

  async getFriendIds(driverId: string): Promise<string[]> {
    this.logger.debug(`Getting friend IDs for driver: ${driverId}`);
    try {
      const friendIds = this.friendships
        .filter((f) => f.driverId === driverId)
        .map((f) => f.friendId);
      this.logger.info(`Found ${friendIds.length} friend IDs for driver: ${driverId}.`);
      return friendIds;
    } catch (error) {
      this.logger.error(`Error getting friend IDs for driver ${driverId}:`, error as Error);
      throw error;
    }
  }

  async getFriends(driverId: string): Promise<Driver[]> {
    this.logger.debug(`Getting friends for driver: ${driverId}`);
    try {
      const ids = await this.getFriendIds(driverId);
      const friends = ids
        .map((id) => this.driversById.get(id))
        .filter((d): d is Driver => Boolean(d));
      this.logger.info(`Found ${friends.length} friends for driver: ${driverId}.`);
      return friends;
    } catch (error) {
      this.logger.error(`Error getting friends for driver ${driverId}:`, error as Error);
      throw error;
    }
  }

  async getSuggestedFriends(driverId: string, limit?: number): Promise<Driver[]> {
    this.logger.debug(`Getting suggested friends for driver: ${driverId}, limit: ${limit}`);
    try {
      const directFriendIds = new Set(await this.getFriendIds(driverId));
      const suggestions = new Map<string, number>();

      for (const friendship of this.friendships) {
        if (!directFriendIds.has(friendship.driverId)) continue;
        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 drivers = rankedIds
        .map((id) => this.driversById.get(id))
        .filter((d): d is Driver => Boolean(d));

      const result = typeof limit === 'number' ? drivers.slice(0, limit) : drivers;
      this.logger.info(`Found ${result.length} suggested friends for driver: ${driverId}.`);
      return result;
    } catch (error) {
      this.logger.error(`Error getting suggested friends for driver ${driverId}:`, error as Error);
      throw error;
    }
  }
}