import { describe, it, expect, beforeEach, vi, type MockedFunction } from 'vitest';
import { Result } from '@core/shared/application/Result';
import type { Logger } from '@core/shared/application/Logger';
import type {
  ReviewProtestUseCase,
  ReviewProtestApplicationError,
  ReviewProtestResult,
} from '@core/racing/application/use-cases/ReviewProtestUseCase';
import { ProtestsService } from './ProtestsService';
import { ReviewProtestPresenter, type ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';

describe('ProtestsService', () => {
  let service: ProtestsService;
  let executeMock: MockedFunction<ReviewProtestUseCase['execute']>;
  let logger: Logger;
  let presenter: ReviewProtestPresenter;

  beforeEach(() => {
    executeMock = vi.fn();
    const reviewProtestUseCase = { execute: executeMock } as unknown as ReviewProtestUseCase;

    presenter = new ReviewProtestPresenter();

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

    service = new ProtestsService(reviewProtestUseCase, presenter, logger);
  });

  const baseCommand = {
    protestId: 'protest-1',
    stewardId: 'steward-1',
    decision: 'uphold' as const,
    decisionNotes: 'Notes',
  };

  it('returns DTO with success model on success', async () => {
    executeMock.mockImplementation(async (command) => {
      presenter.present({ protestId: command.protestId } as ReviewProtestResult);
      return Result.ok(undefined);
    });

    const dto = await service.reviewProtest(baseCommand);

    expect(presenter.getResponseModel()).not.toBeNull();
    expect(executeMock).toHaveBeenCalledWith(baseCommand);
    expect(dto).toEqual<ReviewProtestResponseDTO>({
      success: true,
      protestId: baseCommand.protestId,
      stewardId: baseCommand.stewardId,
      decision: baseCommand.decision,
    });
  });

  it('maps PROTEST_NOT_FOUND error into DTO', async () => {
    const error: ReviewProtestApplicationError = {
      code: 'PROTEST_NOT_FOUND',
      details: { message: 'Protest not found' },
    };

    executeMock.mockImplementation(async () => {
      presenter.presentError(error);
      return Result.err<void, ReviewProtestApplicationError>(error);
    });

    const dto = await service.reviewProtest(baseCommand);

    expect(dto).toEqual<ReviewProtestResponseDTO>({
      success: false,
      errorCode: 'PROTEST_NOT_FOUND',
      message: 'Protest not found',
    });
  });

  it('maps RACE_NOT_FOUND error into DTO', async () => {
    const error: ReviewProtestApplicationError = {
      code: 'RACE_NOT_FOUND',
      details: { message: 'Race not found for protest' },
    };

    executeMock.mockImplementation(async () => {
      presenter.presentError(error);
      return Result.err<void, ReviewProtestApplicationError>(error);
    });

    const dto = await service.reviewProtest(baseCommand);

    expect(dto).toEqual<ReviewProtestResponseDTO>({
      success: false,
      errorCode: 'RACE_NOT_FOUND',
      message: 'Race not found for protest',
    });
  });

  it('maps NOT_LEAGUE_ADMIN error into DTO', async () => {
    const error: ReviewProtestApplicationError = {
      code: 'NOT_LEAGUE_ADMIN',
      details: { message: 'Steward is not authorized to review this protest' },
    };

    executeMock.mockImplementation(async () => {
      presenter.presentError(error);
      return Result.err<void, ReviewProtestApplicationError>(error);
    });

    const dto = await service.reviewProtest(baseCommand);

    expect(dto).toEqual<ReviewProtestResponseDTO>({
      success: false,
      errorCode: 'NOT_LEAGUE_ADMIN',
      message: 'Steward is not authorized to review this protest',
    });
  });

  it('maps unexpected error code into generic failure DTO', async () => {
    const error: ReviewProtestApplicationError = {
      // @ts-expect-error - simulate unexpected error code from core
      code: 'UNEXPECTED',
      details: { message: 'Failed to review protest' },
    };

    executeMock.mockImplementation(async () => {
      presenter.presentError(error);
      return Result.err<void, ReviewProtestApplicationError>(error);
    });

    const dto = await service.reviewProtest(baseCommand);

    expect(dto).toEqual<ReviewProtestResponseDTO>({
      success: false,
      errorCode: 'UNEXPECTED',
      message: 'Failed to review protest',
    });
  });
});
