import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
  TransferLeagueOwnershipUseCase,
  type TransferLeagueOwnershipInput,
  type TransferLeagueOwnershipResult,
  type TransferLeagueOwnershipErrorCode,
} from './TransferLeagueOwnershipUseCase';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { UseCaseOutputPort, Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Result } from '@core/shared/application/Result';

describe('TransferLeagueOwnershipUseCase', () => {
  let leagueRepository: ILeagueRepository;
  let membershipRepository: ILeagueMembershipRepository;
  let logger: Logger & { error: Mock };
  let output: UseCaseOutputPort<TransferLeagueOwnershipResult> & { present: Mock };
  let useCase: TransferLeagueOwnershipUseCase;

  beforeEach(() => {
    leagueRepository = {
      findById: vi.fn(),
      update: vi.fn(),
    } as unknown as ILeagueRepository;

    membershipRepository = {
      getMembership: vi.fn(),
      saveMembership: vi.fn(),
    } as unknown as ILeagueMembershipRepository;

    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    } as unknown as Logger & { error: Mock };

    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<TransferLeagueOwnershipResult> & { present: Mock };

    useCase = new TransferLeagueOwnershipUseCase(
      leagueRepository,
      membershipRepository,
      logger,
      output,
    );
  });

  it('transfers ownership successfully', async () => {
    const mockLeague = {
      id: 'league-1',
      ownerId: { toString: () => 'owner-1' },
      update: vi.fn().mockReturnValue({}),
    } as unknown as { id: string; ownerId: { toString: () => string }; update: Mock };

    const mockNewOwnerMembership = {
      leagueId: 'league-1',
      driverId: 'owner-2',
      status: { toString: () => 'active' },
      role: 'member',
    } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string };

    const mockCurrentOwnerMembership = {
      leagueId: 'league-1',
      driverId: 'owner-1',
      status: { toString: () => 'active' },
      role: 'owner',
    } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string };

    (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague);

    (membershipRepository.getMembership as unknown as Mock)
      .mockResolvedValueOnce(mockNewOwnerMembership)
      .mockResolvedValueOnce(mockCurrentOwnerMembership);

    const input: TransferLeagueOwnershipInput = {
      leagueId: 'league-1',
      currentOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    };

    const result: Result<
      void,
      ApplicationErrorCode<TransferLeagueOwnershipErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(leagueRepository.findById).toHaveBeenCalledWith('league-1');
    expect(membershipRepository.getMembership).toHaveBeenCalledWith('league-1', 'owner-2');
    expect(membershipRepository.getMembership).toHaveBeenCalledWith('league-1', 'owner-1');

    expect(membershipRepository.saveMembership).toHaveBeenCalledTimes(2);

    const saveMembershipMock = membershipRepository.saveMembership as unknown as Mock;
    const firstSaveCall = saveMembershipMock.mock.calls[0]![0] as { role: string };
    const secondSaveCall = saveMembershipMock.mock.calls[1]![0] as { role: string };

    expect(firstSaveCall.role).toBe('owner');
    expect(secondSaveCall.role).toBe('admin');

    expect(mockLeague.update).toHaveBeenCalledWith({ ownerId: 'owner-2' });
    expect(leagueRepository.update).toHaveBeenCalledWith(expect.anything());

    expect(output.present).toHaveBeenCalledTimes(1);

    const presentMock = output.present as Mock;
    const presented = presentMock.mock.calls[0]![0] as TransferLeagueOwnershipResult;
    expect(presented).toEqual({
      leagueId: 'league-1',
      previousOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    });
  });

  it('returns LEAGUE_NOT_FOUND when league does not exist', async () => {
    (leagueRepository.findById as unknown as Mock).mockResolvedValue(null);

    const input: TransferLeagueOwnershipInput = {
      leagueId: 'non-existent',
      currentOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    };

    const result: Result<
      void,
      ApplicationErrorCode<TransferLeagueOwnershipErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      TransferLeagueOwnershipErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('LEAGUE_NOT_FOUND');
    expect(error.details?.message).toContain('non-existent');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns NOT_LEAGUE_OWNER when current owner does not match', async () => {
    const mockLeague = {
      id: 'league-1',
      ownerId: { toString: () => 'other-owner' },
      update: vi.fn(),
    } as unknown as { id: string; ownerId: { toString: () => string }; update: Mock };

    (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague);

    const input: TransferLeagueOwnershipInput = {
      leagueId: 'league-1',
      currentOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    };

    const result: Result<
      void,
      ApplicationErrorCode<TransferLeagueOwnershipErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      TransferLeagueOwnershipErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('NOT_LEAGUE_OWNER');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns NEW_OWNER_NOT_MEMBER when new owner is not an active member', async () => {
    const mockLeague = {
      id: 'league-1',
      ownerId: { toString: () => 'owner-1' },
      update: vi.fn(),
    } as unknown as { id: string; ownerId: { toString: () => string }; update: Mock };

    (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague);

    (membershipRepository.getMembership as unknown as Mock).mockResolvedValue(null);

    const input: TransferLeagueOwnershipInput = {
      leagueId: 'league-1',
      currentOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    };

    const result: Result<
      void,
      ApplicationErrorCode<TransferLeagueOwnershipErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      TransferLeagueOwnershipErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('NEW_OWNER_NOT_MEMBER');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('wraps repository errors in REPOSITORY_ERROR and logs the error', async () => {
    const mockLeague = {
      id: 'league-1',
      ownerId: { toString: () => 'owner-1' },
      update: vi.fn().mockReturnValue({}),
    } as unknown as { id: string; ownerId: { toString: () => string }; update: Mock };

    (leagueRepository.findById as unknown as Mock).mockResolvedValue(mockLeague);

    const mockNewOwnerMembership = {
      leagueId: 'league-1',
      driverId: 'owner-2',
      status: { toString: () => 'active' },
      role: 'member',
    } as unknown as { leagueId: string; driverId: string; status: { toString: () => string }; role: string };

    (membershipRepository.getMembership as unknown as Mock)
      .mockResolvedValueOnce(mockNewOwnerMembership)
      .mockResolvedValueOnce(null);

    const updateError = new Error('update failed');
    (leagueRepository.update as unknown as Mock).mockRejectedValue(updateError);

    const input: TransferLeagueOwnershipInput = {
      leagueId: 'league-1',
      currentOwnerId: 'owner-1',
      newOwnerId: 'owner-2',
    };

    const result: Result<
      void,
      ApplicationErrorCode<TransferLeagueOwnershipErrorCode, { message: string }>
    > = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      TransferLeagueOwnershipErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('REPOSITORY_ERROR');
    expect(error.details?.message).toBe('update failed');

    expect(output.present).not.toHaveBeenCalled();
    expect(logger.error).toHaveBeenCalled();

    const errorMock = logger.error as Mock;
    const calls = errorMock.mock.calls;
    expect(calls.length).toBeGreaterThan(0);
    const loggedMessage = calls[0]?.[0] as string;
    expect(loggedMessage).toContain('update failed');
  });
});
