import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
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';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
import { Race } from '../../domain/entities/Race';
import { Standing } from '../../domain/entities/Standing';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { IStandingRepository } from '../../domain/repositories/IStandingRepository';
import type { Result as RaceResult } from '../../domain/entities/result/Result';

export interface DashboardOverviewInput {
  driverId: string;
}

interface DashboardDriverStatsAdapter {
  rating: number | null;
  wins: number;
  podiums: number;
  totalRaces: number;
  overallRank: number | null;
  consistency: number | null;
}

export interface DashboardDriverSummary {
  driver: Driver;
  avatarUrl: string | null;
  rating: number | null;
  globalRank: number | null;
  totalRaces: number;
  wins: number;
  podiums: number;
  consistency: number | null;
}

export interface DashboardRaceSummary {
  race: Race;
  league: League | null;
  isMyLeague: boolean;
}

export interface DashboardRecentRaceResultSummary {
  race: Race;
  league: League | null;
  result: RaceResult;
}

export interface DashboardLeagueStandingSummary {
  league: League;
  standing: Standing | null;
  totalDrivers: number;
}

export interface DashboardFeedSummary {
  notificationCount: number;
  items: FeedItem[];
}

export interface DashboardFriendSummary {
  driver: Driver;
  avatarUrl: string | null;
}

export interface DashboardOverviewResult {
  currentDriver: DashboardDriverSummary | null;
  myUpcomingRaces: DashboardRaceSummary[];
  otherUpcomingRaces: DashboardRaceSummary[];
  upcomingRaces: DashboardRaceSummary[];
  activeLeaguesCount: number;
  nextRace: DashboardRaceSummary | null;
  recentResults: DashboardRecentRaceResultSummary[];
  leagueStandingsSummaries: DashboardLeagueStandingSummary[];
  feedSummary: DashboardFeedSummary;
  friends: DashboardFriendSummary[];
}

export class DashboardOverviewUseCase {
  constructor(
    private readonly driverRepository: IDriverRepository,
    private readonly raceRepository: IRaceRepository,
    private readonly resultRepository: IResultRepository,
    private readonly leagueRepository: ILeagueRepository,
    private readonly standingRepository: IStandingRepository,
    private readonly leagueMembershipRepository: ILeagueMembershipRepository,
    private readonly raceRegistrationRepository: IRaceRegistrationRepository,
    private readonly feedRepository: IFeedRepository,
    private readonly socialRepository: ISocialGraphRepository,
    private readonly getDriverAvatar: (driverId: string) => Promise<string>,
    private readonly getDriverStats: (
      driverId: string,
    ) => DashboardDriverStatsAdapter | null,
    private readonly output: UseCaseOutputPort<DashboardOverviewResult>,
  ) {}

  async execute(
    input: DashboardOverviewInput,
  ): Promise<
    Result<
      void,
      ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
    >
  > {
    const { driverId } = input;

    try {
      const [driver, allLeagues, allRaces, allResults, feedItems, friends] =
        await Promise.all([
          this.driverRepository.findById(driverId),
          this.leagueRepository.findAll(),
          this.raceRepository.findAll(),
          this.resultRepository.findAll(),
          this.feedRepository.getFeedForDriver(driverId),
          this.socialRepository.getFriends(driverId),
        ]);

      if (!driver) {
        return Result.err({
          code: 'DRIVER_NOT_FOUND',
          details: { message: 'Driver not found' },
        });
      }

      const leagueMap = new Map(allLeagues.map(league => [league.id.toString(), league]));

      const driverStats = this.getDriverStats(driverId);

      const currentDriver: DashboardDriverSummary = {
        driver,
        avatarUrl: await this.getDriverAvatar(driver.id),
        rating: driverStats?.rating ?? null,
        globalRank: driverStats?.overallRank ?? null,
        totalRaces: driverStats?.totalRaces ?? 0,
        wins: driverStats?.wins ?? 0,
        podiums: driverStats?.podiums ?? 0,
        consistency: driverStats?.consistency ?? null,
      };

      const driverLeagues = await this.getDriverLeagues(allLeagues, driverId);
      const driverLeagueIds = new Set(driverLeagues.map(league => league.id.toString()));

      const now = new Date();
      const upcomingRaces = allRaces
        .filter(race => race.status.isScheduled() && race.scheduledAt > now)
        .sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());

      const upcomingRacesInDriverLeagues = upcomingRaces.filter(race =>
        driverLeagueIds.has(race.leagueId.toString()),
      );

      const { myUpcomingRaces, otherUpcomingRaces } =
        await this.partitionUpcomingRacesByRegistration(
          upcomingRacesInDriverLeagues,
          driverId,
          leagueMap,
        );

      const nextRace: DashboardRaceSummary | null =
        myUpcomingRaces.length > 0 ? myUpcomingRaces[0]! : null;

      const upcomingRacesSummaries: DashboardRaceSummary[] = [
        ...myUpcomingRaces,
        ...otherUpcomingRaces,
      ]
        .slice()
        .sort(
          (a, b) =>
            a.race.scheduledAt.getTime() - b.race.scheduledAt.getTime(),
        );

      const recentResults = this.buildRecentResults(
        allResults,
        allRaces,
        allLeagues,
        driverId,
      );

      const leagueStandingsSummaries = await this.buildLeagueStandingsSummaries(
        driverLeagues,
        driverId,
      );

      const activeLeaguesCount = this.computeActiveLeaguesCount(
        upcomingRacesSummaries,
        leagueStandingsSummaries,
      );

      const feedSummary = this.buildFeedSummary(feedItems);

      const friendsSummary = await this.buildFriendsSummary(friends);

      const result: DashboardOverviewResult = {
        currentDriver,
        myUpcomingRaces,
        otherUpcomingRaces,
        upcomingRaces: upcomingRacesSummaries,
        activeLeaguesCount,
        nextRace,
        recentResults,
        leagueStandingsSummaries,
        feedSummary,
        friends: friendsSummary,
      };

      this.output.present(result);

      return Result.ok(undefined);
    } catch (error) {
      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message: error instanceof Error ? error.message : 'Unknown error',
        },
      });
    }
  }

  private async getDriverLeagues(
    allLeagues: League[],
    driverId: string,
  ): Promise<League[]> {
    const driverLeagues: League[] = [];

    for (const league of allLeagues) {
      const membership = await this.leagueMembershipRepository.getMembership(
        league.id.toString(),
        driverId,
      );
      if (membership && membership.status.toString() === 'active') {
        driverLeagues.push(league);
      }
    }

    return driverLeagues;
  }

  private async partitionUpcomingRacesByRegistration(
    upcomingRaces: Race[],
    driverId: string,
    leagueMap: Map<string, League>,
  ): Promise<{
    myUpcomingRaces: DashboardRaceSummary[];
    otherUpcomingRaces: DashboardRaceSummary[];
  }> {
    const myUpcomingRaces: DashboardRaceSummary[] = [];
    const otherUpcomingRaces: DashboardRaceSummary[] = [];

    for (const race of upcomingRaces) {
      const isRegistered = await this.raceRegistrationRepository.isRegistered(
        race.id,
        driverId,
      );
      const summary = this.mapRaceToSummary(race, leagueMap, true);

      if (isRegistered) {
        myUpcomingRaces.push(summary);
      } else {
        otherUpcomingRaces.push(summary);
      }
    }

    return { myUpcomingRaces, otherUpcomingRaces };
  }

  private mapRaceToSummary(
    race: Race,
    leagueMap: Map<string, League>,
    isMyLeague: boolean,
  ): DashboardRaceSummary {
    return {
      race,
      league: leagueMap.get(race.leagueId) ?? null,
      isMyLeague,
    };
  }

  private buildRecentResults(
    allResults: RaceResult[],
    allRaces: Race[],
    allLeagues: League[],
    driverId: string,
  ): DashboardRecentRaceResultSummary[] {
    const raceById = new Map(allRaces.map(race => [race.id, race]));
    const leagueById = new Map(allLeagues.map(league => [league.id.toString(), league]));

    const driverResults = allResults.filter(result => result.driverId.toString() === driverId);

    const enriched = driverResults
      .map(result => {
        const race = raceById.get(result.raceId.toString());
        if (!race) return null;

        const league = leagueById.get(race.leagueId) ?? null;

        const item: DashboardRecentRaceResultSummary = {
          race,
          league,
          result,
        };

        return item;
      })
      .filter((item): item is DashboardRecentRaceResultSummary => !!item)
      .sort(
        (a, b) =>
          b.race.scheduledAt.getTime() - a.race.scheduledAt.getTime(),
      );

    const RECENT_RESULTS_LIMIT = 5;

    return enriched.slice(0, RECENT_RESULTS_LIMIT);
  }

  private async buildLeagueStandingsSummaries(
    driverLeagues: League[],
    driverId: string,
  ): Promise<DashboardLeagueStandingSummary[]> {
    const summaries: DashboardLeagueStandingSummary[] = [];

    for (const league of driverLeagues.slice(0, 3)) {
      const standings = await this.standingRepository.findByLeagueId(league.id.toString());
      const driverStanding = standings.find(
        (standing: Standing) => standing.driverId.toString() === driverId,
      );

      summaries.push({
        league,
        standing: driverStanding ?? null,
        totalDrivers: standings.length,
      });
    }

    return summaries;
  }

  private computeActiveLeaguesCount(
    upcomingRaces: DashboardRaceSummary[],
    leagueStandingsSummaries: DashboardLeagueStandingSummary[],
  ): number {
    const activeLeagueIds = new Set<string>();

    for (const race of upcomingRaces) {
      activeLeagueIds.add(race.race.leagueId);
    }

    for (const standing of leagueStandingsSummaries) {
      activeLeagueIds.add(standing.league.id.toString());
    }

    return activeLeagueIds.size;
  }

  private buildFeedSummary(feedItems: FeedItem[]): DashboardFeedSummary {
    return {
      notificationCount: feedItems.length,
      items: feedItems,
    };
  }

  private async buildFriendsSummary(
    friends: Driver[],
  ): Promise<DashboardFriendSummary[]> {
    const friendSummaries: DashboardFriendSummary[] = [];

    for (const friend of friends) {
      const avatarUrl = await this.getDriverAvatar(friend.id);
      friendSummaries.push({
        driver: friend,
        avatarUrl,
      });
    }

    return friendSummaries;
  }
}
