/**
 * Integration Test: Team Membership Use Case Orchestration
 * 
 * Tests the orchestration logic of team membership-related Use Cases:
 * - JoinTeamUseCase: Allows driver to request to join a team
 * - LeaveTeamUseCase: Allows driver to leave a team
 * - GetTeamMembershipUseCase: Retrieves driver's membership in a team
 * - GetTeamMembersUseCase: Retrieves all team members
 * - GetTeamJoinRequestsUseCase: Retrieves pending join requests
 * - ApproveTeamJoinRequestUseCase: Admin approves join request
 * - Validates that Use Cases correctly interact with their Ports (Repositories)
 * - Uses In-Memory adapters for fast, deterministic testing
 * 
 * Focus: Business logic orchestration, NOT UI rendering
 */

import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { JoinTeamUseCase } from '../../../core/racing/application/use-cases/JoinTeamUseCase';
import { LeaveTeamUseCase } from '../../../core/racing/application/use-cases/LeaveTeamUseCase';
import { GetTeamMembershipUseCase } from '../../../core/racing/application/use-cases/GetTeamMembershipUseCase';
import { GetTeamMembersUseCase } from '../../../core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '../../../core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { ApproveTeamJoinRequestUseCase } from '../../../core/racing/application/use-cases/ApproveTeamJoinRequestUseCase';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { Logger } from '../../../core/shared/domain/Logger';

describe('Team Membership Use Case Orchestration', () => {
  let teamRepository: InMemoryTeamRepository;
  let membershipRepository: InMemoryTeamMembershipRepository;
  let driverRepository: InMemoryDriverRepository;
  let joinTeamUseCase: JoinTeamUseCase;
  let leaveTeamUseCase: LeaveTeamUseCase;
  let getTeamMembershipUseCase: GetTeamMembershipUseCase;
  let getTeamMembersUseCase: GetTeamMembersUseCase;
  let getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase;
  let approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase;
  let mockLogger: Logger;

  beforeAll(() => {
    mockLogger = {
      info: () => {},
      debug: () => {},
      warn: () => {},
      error: () => {},
    } as unknown as Logger;
    
    teamRepository = new InMemoryTeamRepository(mockLogger);
    membershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
    driverRepository = new InMemoryDriverRepository(mockLogger);
    
    joinTeamUseCase = new JoinTeamUseCase(teamRepository, membershipRepository, mockLogger);
    leaveTeamUseCase = new LeaveTeamUseCase(teamRepository, membershipRepository, mockLogger);
    getTeamMembershipUseCase = new GetTeamMembershipUseCase(membershipRepository, mockLogger);
    getTeamMembersUseCase = new GetTeamMembersUseCase(membershipRepository, driverRepository, teamRepository, mockLogger);
    getTeamJoinRequestsUseCase = new GetTeamJoinRequestsUseCase(membershipRepository, driverRepository, teamRepository);
    approveTeamJoinRequestUseCase = new ApproveTeamJoinRequestUseCase(membershipRepository);
  });

  beforeEach(() => {
    teamRepository.clear();
    membershipRepository.clear();
    driverRepository.clear();
  });

  describe('JoinTeamUseCase - Success Path', () => {
    it('should create a join request for a team', async () => {
      // Scenario: Driver requests to join team
      // Given: A driver exists
      const driverId = 'd1';
      const driver = Driver.create({ id: driverId, iracingId: '1', name: 'Driver 1', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't1';
      const team = Team.create({ id: teamId, name: 'Team 1', tag: 'T1', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: The team has available roster slots
      // (Team has no members yet, so it has available slots)
      
      // When: JoinTeamUseCase.execute() is called
      const result = await joinTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: A join request should be created
      expect(result.isOk()).toBe(true);
      const { team: resultTeam, membership } = result.unwrap();
      expect(resultTeam.id.toString()).toBe(teamId);
      
      // And: The request should be in pending status
      expect(membership.status).toBe('active');
      expect(membership.role).toBe('driver');
      
      // And: The membership should be in the repository
      const savedMembership = await membershipRepository.getMembership(teamId, driverId);
      expect(savedMembership).toBeDefined();
      expect(savedMembership?.status).toBe('active');
    });

    it('should create a join request when team is not full', async () => {
      // Scenario: Team has available slots
      // Given: A driver exists
      const driverId = 'd2';
      const driver = Driver.create({ id: driverId, iracingId: '2', name: 'Driver 2', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists with available roster slots
      const teamId = 't2';
      const team = Team.create({ id: teamId, name: 'Team 2', tag: 'T2', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // When: JoinTeamUseCase.execute() is called
      const result = await joinTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: A join request should be created
      expect(result.isOk()).toBe(true);
      const { membership } = result.unwrap();
      expect(membership.status).toBe('active');
    });
  });

  describe('JoinTeamUseCase - Validation', () => {
    it('should reject join request when driver is already a member', async () => {
      // Scenario: Driver already member
      // Given: A driver exists
      const driverId = 'd3';
      const driver = Driver.create({ id: driverId, iracingId: '3', name: 'Driver 3', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't3';
      const team = Team.create({ id: teamId, name: 'Team 3', tag: 'T3', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: The driver is already a member of the team
      await membershipRepository.saveMembership({
        teamId,
        driverId,
        role: 'driver',
        status: 'active',
        joinedAt: new Date()
      });
      
      // When: JoinTeamUseCase.execute() is called
      const result = await joinTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('ALREADY_MEMBER');
    });

    it('should reject join request when driver already has pending request', async () => {
      // Scenario: Driver has pending request
      // Given: A driver exists
      const driverId = 'd4';
      const driver = Driver.create({ id: driverId, iracingId: '4', name: 'Driver 4', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't4';
      const team = Team.create({ id: teamId, name: 'Team 4', tag: 'T4', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: The driver already has a pending join request for the team
      await membershipRepository.saveJoinRequest({
        id: 'jr1',
        teamId,
        driverId,
        status: 'pending',
        requestedAt: new Date()
      });
      
      // When: JoinTeamUseCase.execute() is called
      const result = await joinTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('ALREADY_MEMBER');
    });
  });

  describe('JoinTeamUseCase - Error Handling', () => {
    it('should throw error when driver does not exist', async () => {
      // Scenario: Non-existent driver
      // Given: No driver exists with the given ID
      const nonExistentDriverId = 'nonexistent';
      
      // And: A team exists
      const teamId = 't5';
      const team = Team.create({ id: teamId, name: 'Team 5', tag: 'T5', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // When: JoinTeamUseCase.execute() is called with non-existent driver ID
      const result = await joinTeamUseCase.execute({
        teamId,
        driverId: nonExistentDriverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('TEAM_NOT_FOUND');
    });

    it('should throw error when team does not exist', async () => {
      // Scenario: Non-existent team
      // Given: A driver exists
      const driverId = 'd6';
      const driver = Driver.create({ id: driverId, iracingId: '6', name: 'Driver 6', country: 'US' });
      await driverRepository.create(driver);
      
      // And: No team exists with the given ID
      const nonExistentTeamId = 'nonexistent';
      
      // When: JoinTeamUseCase.execute() is called with non-existent team ID
      const result = await joinTeamUseCase.execute({
        teamId: nonExistentTeamId,
        driverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('TEAM_NOT_FOUND');
    });
  });

  describe('LeaveTeamUseCase - Success Path', () => {
    it('should allow driver to leave team', async () => {
      // Scenario: Driver leaves team
      // Given: A driver exists
      const driverId = 'd7';
      const driver = Driver.create({ id: driverId, iracingId: '7', name: 'Driver 7', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't7';
      const team = Team.create({ id: teamId, name: 'Team 7', tag: 'T7', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: The driver is a member of the team
      await membershipRepository.saveMembership({
        teamId,
        driverId,
        role: 'driver',
        status: 'active',
        joinedAt: new Date()
      });
      
      // When: LeaveTeamUseCase.execute() is called
      const result = await leaveTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: The driver should be removed from the team
      expect(result.isOk()).toBe(true);
      const { team: resultTeam, previousMembership } = result.unwrap();
      expect(resultTeam.id.toString()).toBe(teamId);
      expect(previousMembership.driverId).toBe(driverId);
      
      // And: The membership should be removed from the repository
      const savedMembership = await membershipRepository.getMembership(teamId, driverId);
      expect(savedMembership).toBeNull();
    });
  });

  describe('LeaveTeamUseCase - Validation', () => {
    it('should reject leave when driver is not a member', async () => {
      // Scenario: Driver not member
      // Given: A driver exists
      const driverId = 'd8';
      const driver = Driver.create({ id: driverId, iracingId: '8', name: 'Driver 8', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't8';
      const team = Team.create({ id: teamId, name: 'Team 8', tag: 'T8', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // When: LeaveTeamUseCase.execute() is called
      const result = await leaveTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('NOT_MEMBER');
    });

    it('should reject leave when driver is team owner', async () => {
      // Scenario: Team owner cannot leave
      // Given: A driver exists
      const driverId = 'd9';
      const driver = Driver.create({ id: driverId, iracingId: '9', name: 'Driver 9', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists with the driver as owner
      const teamId = 't9';
      const team = Team.create({ id: teamId, name: 'Team 9', tag: 'T9', description: 'Test Team', ownerId: driverId, leagues: [] });
      await teamRepository.create(team);
      
      // And: The driver is the owner
      await membershipRepository.saveMembership({
        teamId,
        driverId,
        role: 'owner',
        status: 'active',
        joinedAt: new Date()
      });
      
      // When: LeaveTeamUseCase.execute() is called
      const result = await leaveTeamUseCase.execute({
        teamId,
        driverId
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('OWNER_CANNOT_LEAVE');
    });
  });

  describe('GetTeamMembershipUseCase - Success Path', () => {
    it('should retrieve driver membership in team', async () => {
      // Scenario: Retrieve membership
      // Given: A driver exists
      const driverId = 'd10';
      const driver = Driver.create({ id: driverId, iracingId: '10', name: 'Driver 10', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't10';
      const team = Team.create({ id: teamId, name: 'Team 10', tag: 'T10', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: The driver is a member of the team
      await membershipRepository.saveMembership({
        teamId,
        driverId,
        role: 'driver',
        status: 'active',
        joinedAt: new Date()
      });
      
      // When: GetTeamMembershipUseCase.execute() is called
      const result = await getTeamMembershipUseCase.execute({
        teamId,
        driverId
      });

      // Then: It should return the membership
      expect(result.isOk()).toBe(true);
      const { membership } = result.unwrap();
      expect(membership).toBeDefined();
      expect(membership?.role).toBe('member');
      expect(membership?.isActive).toBe(true);
    });

    it('should return null when driver is not a member', async () => {
      // Scenario: No membership found
      // Given: A driver exists
      const driverId = 'd11';
      const driver = Driver.create({ id: driverId, iracingId: '11', name: 'Driver 11', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A team exists
      const teamId = 't11';
      const team = Team.create({ id: teamId, name: 'Team 11', tag: 'T11', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // When: GetTeamMembershipUseCase.execute() is called
      const result = await getTeamMembershipUseCase.execute({
        teamId,
        driverId
      });

      // Then: It should return null
      expect(result.isOk()).toBe(true);
      const { membership } = result.unwrap();
      expect(membership).toBeNull();
    });
  });

  describe('GetTeamMembersUseCase - Success Path', () => {
    it('should retrieve all team members', async () => {
      // Scenario: Retrieve team members
      // Given: A team exists
      const teamId = 't12';
      const team = Team.create({ id: teamId, name: 'Team 12', tag: 'T12', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: Multiple drivers exist
      const driver1 = Driver.create({ id: 'd12', iracingId: '12', name: 'Driver 12', country: 'US' });
      const driver2 = Driver.create({ id: 'd13', iracingId: '13', name: 'Driver 13', country: 'UK' });
      await driverRepository.create(driver1);
      await driverRepository.create(driver2);
      
      // And: Drivers are members of the team
      await membershipRepository.saveMembership({
        teamId,
        driverId: 'd12',
        role: 'owner',
        status: 'active',
        joinedAt: new Date()
      });
      await membershipRepository.saveMembership({
        teamId,
        driverId: 'd13',
        role: 'driver',
        status: 'active',
        joinedAt: new Date()
      });
      
      // When: GetTeamMembersUseCase.execute() is called
      const result = await getTeamMembersUseCase.execute({
        teamId
      });

      // Then: It should return all team members
      expect(result.isOk()).toBe(true);
      const { team: resultTeam, members } = result.unwrap();
      expect(resultTeam.id.toString()).toBe(teamId);
      expect(members).toHaveLength(2);
      expect(members[0].membership.driverId).toBe('d12');
      expect(members[1].membership.driverId).toBe('d13');
    });
  });

  describe('GetTeamJoinRequestsUseCase - Success Path', () => {
    it('should retrieve pending join requests', async () => {
      // Scenario: Retrieve join requests
      // Given: A team exists
      const teamId = 't14';
      const team = Team.create({ id: teamId, name: 'Team 14', tag: 'T14', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: Multiple drivers exist
      const driver1 = Driver.create({ id: 'd14', iracingId: '14', name: 'Driver 14', country: 'US' });
      const driver2 = Driver.create({ id: 'd15', iracingId: '15', name: 'Driver 15', country: 'UK' });
      await driverRepository.create(driver1);
      await driverRepository.create(driver2);
      
      // And: Drivers have pending join requests
      await membershipRepository.saveJoinRequest({
        id: 'jr2',
        teamId,
        driverId: 'd14',
        status: 'pending',
        requestedAt: new Date()
      });
      await membershipRepository.saveJoinRequest({
        id: 'jr3',
        teamId,
        driverId: 'd15',
        status: 'pending',
        requestedAt: new Date()
      });
      
      // When: GetTeamJoinRequestsUseCase.execute() is called
      const result = await getTeamJoinRequestsUseCase.execute({
        teamId
      });

      // Then: It should return the join requests
      expect(result.isOk()).toBe(true);
      const { team: resultTeam, joinRequests } = result.unwrap();
      expect(resultTeam.id.toString()).toBe(teamId);
      expect(joinRequests).toHaveLength(2);
      expect(joinRequests[0].driverId).toBe('d14');
      expect(joinRequests[1].driverId).toBe('d15');
    });
  });

  describe('ApproveTeamJoinRequestUseCase - Success Path', () => {
    it('should approve a pending join request', async () => {
      // Scenario: Admin approves join request
      // Given: A team exists
      const teamId = 't16';
      const team = Team.create({ id: teamId, name: 'Team 16', tag: 'T16', description: 'Test Team', ownerId: 'owner', leagues: [] });
      await teamRepository.create(team);
      
      // And: A driver exists
      const driverId = 'd16';
      const driver = Driver.create({ id: driverId, iracingId: '16', name: 'Driver 16', country: 'US' });
      await driverRepository.create(driver);
      
      // And: A driver has a pending join request for the team
      await membershipRepository.saveJoinRequest({
        id: 'jr4',
        teamId,
        driverId,
        status: 'pending',
        requestedAt: new Date()
      });
      
      // When: ApproveTeamJoinRequestUseCase.execute() is called
      const result = await approveTeamJoinRequestUseCase.execute({
        teamId,
        requestId: 'jr4'
      });

      // Then: The join request should be approved
      expect(result.isOk()).toBe(true);
      const { membership } = result.unwrap();
      expect(membership.driverId).toBe(driverId);
      expect(membership.teamId).toBe(teamId);
      expect(membership.status).toBe('active');
      
      // And: The driver should be added to the team roster
      const savedMembership = await membershipRepository.getMembership(teamId, driverId);
      expect(savedMembership).toBeDefined();
      expect(savedMembership?.status).toBe('active');
    });
  });
});
