import { Driver } from '@core/racing/domain/entities/Driver'; import { League } from '@core/racing/domain/entities/League'; import { Team } from '@core/racing/domain/entities/Team'; import { MediaReference } from '@core/domain/media/MediaReference'; import type { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership'; import { faker } from '@faker-js/faker'; import { seedId } from './SeedIdHelper'; export interface TeamStats { performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; specialization: 'endurance' | 'sprint' | 'mixed'; region: string; languages: string[]; totalWins: number; totalRaces: number; rating: number; category?: string; } export class RacingTeamFactory { constructor( private readonly baseDate: Date, private readonly persistence: 'postgres' | 'inmemory' = 'inmemory', ) {} createTeams(drivers: Driver[], leagues: League[]): Team[] { const teamCount = 50; // Increased from 15 to 50 return Array.from({ length: teamCount }, (_, idx) => { const i = idx + 1; const owner = faker.helpers.arrayElement(drivers); const teamLeagues = faker.helpers.arrayElements( leagues.map((l) => l.id.toString()), { min: 0, max: 3 }, ); // 30-50% of teams are recruiting const isRecruiting = faker.datatype.boolean({ probability: 0.4 }); const teamId = seedId(`team-${i}`, this.persistence); return Team.create({ id: teamId, name: faker.company.name() + ' Racing', tag: faker.string.alpha({ length: 4, casing: 'upper' }), description: faker.lorem.sentences(2), ownerId: owner.id, leagues: teamLeagues, isRecruiting, createdAt: faker.date.past({ years: 2, refDate: this.baseDate }), logoRef: MediaReference.generated('team', teamId), }); }); } createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] { const memberships: TeamMembership[] = []; const usedDrivers = new Set(); teams.forEach((team, teamIndex) => { const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString()); // Create varied team compositions let memberCount: number; let hasManager: boolean; if (teamIndex % 5 === 0) { // Solo teams (just owner) memberCount = 0; hasManager = false; } else if (teamIndex % 5 === 1) { // Small teams (2-3 members) memberCount = faker.number.int({ min: 1, max: 2 }); hasManager = faker.datatype.boolean(); } else if (teamIndex % 5 === 2) { // Medium teams (3-5 members) memberCount = faker.number.int({ min: 2, max: 4 }); hasManager = true; } else if (teamIndex % 5 === 3) { // Large teams (5-7 members) memberCount = faker.number.int({ min: 4, max: 6 }); hasManager = true; } else { // Mixed - sometimes with manager, sometimes without memberCount = faker.number.int({ min: 1, max: 5 }); hasManager = faker.datatype.boolean(); } const members = faker.helpers.arrayElements(availableDrivers, memberCount); // Add owner memberships.push({ teamId: team.id.toString(), driverId: team.ownerId.toString(), role: 'owner', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(team.ownerId.toString()); // Add manager if needed if (hasManager && members.length > 0) { const managerIndex = faker.number.int({ min: 0, max: members.length - 1 }); const manager = members[managerIndex]!; memberships.push({ teamId: team.id.toString(), driverId: manager.id.toString(), role: 'manager', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(manager.id.toString()); } // Add remaining members as drivers members.forEach((driver) => { if (!usedDrivers.has(driver.id.toString())) { memberships.push({ teamId: team.id.toString(), driverId: driver.id.toString(), role: 'driver', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(driver.id.toString()); } }); }); return memberships; } createTeamJoinRequests( drivers: Driver[], teams: Team[], teamMemberships: TeamMembership[], ): TeamJoinRequest[] { const membershipIds = new Set(teamMemberships.map(m => `${m.teamId}:${m.driverId}`)); const requests: TeamJoinRequest[] = []; const addRequest = (request: TeamJoinRequest): void => { requests.push(request); }; const team1 = teams[0]; if (team1) { const candidateDriverIds = drivers .map(d => d.id.toString()) .filter(driverId => !membershipIds.has(`${team1.id.toString()}:${driverId}`)) .slice(0, 8); candidateDriverIds.forEach((driverId, idx) => { addRequest({ id: seedId(`team-join-${team1.id.toString()}-${driverId}`, this.persistence), teamId: team1.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -(5 + idx)), ...(idx % 3 === 0 ? { message: 'Can I join as a substitute driver for endurance events?' } : idx % 3 === 2 ? { message: '' } : {}), }); }); // Conflict edge case: owner submits a join request to own team. addRequest({ id: seedId(`team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`, this.persistence), teamId: team1.id.toString(), driverId: team1.ownerId.toString(), requestedAt: this.addDays(this.baseDate, -1), message: 'Testing edge case: owner submitted join request.', }); } const team3 = teams[2]; if (team3 && drivers[0]) { const driverId = drivers[0].id.toString(); addRequest({ id: seedId('dup-team-join-req-1', this.persistence), teamId: team3.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -10), message: 'First request message (will be overwritten).', }); addRequest({ id: seedId('dup-team-join-req-1', this.persistence), teamId: team3.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -9), message: 'Updated request message (duplicate id).', }); } return requests; } /** * Generate team statistics and metadata for display * This would be stored in a separate stats table/service in production */ generateTeamStats(teams: Team[]): Map { const statsMap = new Map(); // Available regions const regions = ['Europe', 'North America', 'South America', 'Asia', 'Oceania', 'Africa']; // Available languages const allLanguages = ['English', 'German', 'French', 'Spanish', 'Italian', 'Portuguese', 'Japanese', 'Korean', 'Russian', 'Chinese']; teams.forEach((team, idx) => { const i = idx + 1; // Determine performance level based on index let performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; let totalRaces: number; let rating: number; let totalWins: number; if (i % 8 === 0) { // Pro teams performanceLevel = 'pro'; totalRaces = faker.number.int({ min: 80, max: 150 }); rating = faker.number.int({ min: 1700, max: 2000 }); totalWins = faker.number.int({ min: 15, max: 40 }); } else if (i % 5 === 0) { // Advanced teams performanceLevel = 'advanced'; totalRaces = faker.number.int({ min: 40, max: 100 }); rating = faker.number.int({ min: 1500, max: 1800 }); totalWins = faker.number.int({ min: 8, max: 20 }); } else if (i % 3 === 0) { // Intermediate teams performanceLevel = 'intermediate'; totalRaces = faker.number.int({ min: 20, max: 60 }); rating = faker.number.int({ min: 1200, max: 1600 }); totalWins = faker.number.int({ min: 3, max: 12 }); } else { // Beginner teams performanceLevel = 'beginner'; totalRaces = faker.number.int({ min: 5, max: 25 }); rating = faker.number.int({ min: 900, max: 1300 }); totalWins = faker.number.int({ min: 0, max: 5 }); } // Determine specialization let specialization: 'endurance' | 'sprint' | 'mixed'; if (i % 7 === 0) { specialization = 'endurance'; } else if (i % 4 === 0) { specialization = 'sprint'; } else { specialization = 'mixed'; } // Determine category - use all available categories const categories = ['beginner', 'intermediate', 'advanced', 'pro', 'endurance', 'sprint']; const category = faker.helpers.arrayElement(categories); // Generate region and languages const region = faker.helpers.arrayElement(regions); const languageCount = faker.number.int({ min: 1, max: 3 }); const languages = faker.helpers.arrayElements(allLanguages, languageCount); statsMap.set(team.id.toString(), { performanceLevel, specialization, region, languages, totalWins, totalRaces, rating, category, }); }); return statsMap; } private addDays(date: Date, days: number): Date { return new Date(date.getTime() + days * 24 * 60 * 60 * 1000); } }