/**
 * Get Team Rankings Use Case
 *
 * Orchestrates the retrieval of team rankings data.
 * Aggregates data from repositories and returns teams with search, filter, and sort capabilities.
 */

import { LeaderboardsRepository } from '../ports/LeaderboardsRepository';
import { LeaderboardsEventPublisher } from '../ports/LeaderboardsEventPublisher';
import {
  TeamRankingsQuery,
  TeamRankingsResult,
  TeamRankingEntry,
  PaginationMetadata,
} from '../ports/TeamRankingsQuery';
import { ValidationError } from '../../../shared/errors/ValidationError';

export interface GetTeamRankingsUseCasePorts {
  leaderboardsRepository: LeaderboardsRepository;
  eventPublisher: LeaderboardsEventPublisher;
}

export class GetTeamRankingsUseCase {
  constructor(private readonly ports: GetTeamRankingsUseCasePorts) {}

  async execute(query: TeamRankingsQuery = {}): Promise<TeamRankingsResult> {
    try {
      // Validate query parameters
      this.validateQuery(query);

      const page = query.page ?? 1;
      const limit = query.limit ?? 20;

      // Fetch all teams and drivers for member count aggregation
      const [allTeams, allDrivers] = await Promise.all([
        this.ports.leaderboardsRepository.findAllTeams(),
        this.ports.leaderboardsRepository.findAllDrivers(),
      ]);

      // Count members from drivers
      const driverCounts = new Map<string, number>();
      allDrivers.forEach(driver => {
        if (driver.teamId) {
          driverCounts.set(driver.teamId, (driverCounts.get(driver.teamId) || 0) + 1);
        }
      });

      // Map teams from repository
      const teamsWithAggregatedData = allTeams.map(team => {
        const countFromDrivers = driverCounts.get(team.id);
        return {
          ...team,
          // If drivers exist in repository for this team, use that count as source of truth.
          // Otherwise, fall back to the memberCount property on the team itself.
          memberCount: countFromDrivers !== undefined ? countFromDrivers : (team.memberCount || 0)
        };
      });

      // Discover teams that only exist in the drivers repository
      const discoveredTeams: any[] = [];
      driverCounts.forEach((count, teamId) => {
        if (!allTeams.some(t => t.id === teamId)) {
          const driverWithTeam = allDrivers.find(d => d.teamId === teamId);
          discoveredTeams.push({
            id: teamId,
            name: driverWithTeam?.teamName || `Team ${teamId}`,
            rating: 0,
            memberCount: count,
            raceCount: 0
          });
        }
      });

      const finalTeams = [...teamsWithAggregatedData, ...discoveredTeams];

      // Apply search filter
      let filteredTeams = finalTeams;
      if (query.search) {
        const searchLower = query.search.toLowerCase();
        filteredTeams = filteredTeams.filter((team) =>
          team.name.toLowerCase().includes(searchLower),
        );
      }

      // Apply rating filter
      if (query.minRating !== undefined) {
        filteredTeams = filteredTeams.filter(
          (team) => team.rating >= query.minRating!,
        );
      }

      // Apply member count filter
      if (query.minMemberCount !== undefined) {
        filteredTeams = filteredTeams.filter(
          (team) => team.memberCount >= query.minMemberCount!,
        );
      }

      // Sort teams
      const sortBy = query.sortBy ?? 'rating';
      const sortOrder = query.sortOrder ?? 'desc';

      filteredTeams.sort((a, b) => {
        let comparison = 0;

        switch (sortBy) {
          case 'rating':
            comparison = a.rating - b.rating;
            break;
          case 'name':
            comparison = a.name.localeCompare(b.name);
            break;
          case 'rank':
            comparison = 0;
            break;
          case 'memberCount':
            comparison = a.memberCount - b.memberCount;
            break;
        }

        // If primary sort is equal, always use name ASC as secondary sort
        if (comparison === 0 && sortBy !== 'name') {
          return a.name.localeCompare(b.name);
        }

        return sortOrder === 'asc' ? comparison : -comparison;
      });

      // Calculate pagination
      const total = filteredTeams.length;
      const totalPages = Math.ceil(total / limit);
      const startIndex = (page - 1) * limit;
      const endIndex = Math.min(startIndex + limit, total);

      // Get paginated teams
      const paginatedTeams = filteredTeams.slice(startIndex, endIndex);

      // Map to ranking entries with rank
      const teamEntries: TeamRankingEntry[] = paginatedTeams.map(
        (team, index): TeamRankingEntry => ({
          rank: startIndex + index + 1,
          id: team.id,
          name: team.name,
          rating: team.rating,
          memberCount: team.memberCount,
          raceCount: team.raceCount,
        }),
      );

      // Publish event
      await this.ports.eventPublisher.publishTeamRankingsAccessed({
        type: 'team_rankings_accessed',
        timestamp: new Date(),
      });

      return {
        teams: teamEntries,
        pagination: {
          total,
          page,
          limit,
          totalPages,
        },
      };
    } catch (error) {
      // Publish error event
      await this.ports.eventPublisher.publishLeaderboardsError({
        type: 'leaderboards_error',
        error: error instanceof Error ? error.message : String(error),
        timestamp: new Date(),
      });
      throw error;
    }
  }

  private validateQuery(query: TeamRankingsQuery): void {
    if (query.page !== undefined && query.page < 1) {
      throw new ValidationError('Page must be a positive integer');
    }

    if (query.limit !== undefined && query.limit < 1) {
      throw new ValidationError('Limit must be a positive integer');
    }

    if (query.minRating !== undefined && query.minRating < 0) {
      throw new ValidationError('Min rating must be a non-negative number');
    }

    if (query.minMemberCount !== undefined && query.minMemberCount < 0) {
      throw new ValidationError('Min member count must be a non-negative number');
    }

    if (query.sortBy && !['rating', 'name', 'rank', 'memberCount'].includes(query.sortBy)) {
      throw new ValidationError('Invalid sort field');
    }

    if (query.sortOrder && !['asc', 'desc'].includes(query.sortOrder)) {
      throw new ValidationError('Sort order must be "asc" or "desc"');
    }
  }
}
