import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { Result } from '@gridpilot/shared/application/Result';
import { ConfirmCheckoutUseCase } from 'apps/companion/main/automation/application/use-cases/ConfirmCheckoutUseCase';
import type { CheckoutServicePort } from 'apps/companion/main/automation/application/ports/CheckoutServicePort';
import type { CheckoutConfirmationPort } from 'apps/companion/main/automation/application/ports/CheckoutConfirmationPort';
import { CheckoutPrice } from 'apps/companion/main/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState } from 'apps/companion/main/automation/domain/value-objects/CheckoutState';
import { CheckoutConfirmation } from 'apps/companion/main/automation/domain/value-objects/CheckoutConfirmation';
import type { Logger } from '@core/shared/application';

/**
 * ConfirmCheckoutUseCase - GREEN PHASE
 *
 * Tests for checkout confirmation flow including price extraction,
 * insufficient funds detection, and user confirmation.
 */

describe('ConfirmCheckoutUseCase', () => {
  let mockCheckoutService: {
    extractCheckoutInfo: Mock;
    proceedWithCheckout: Mock;
  };
  let mockConfirmationPort: {
    requestCheckoutConfirmation: Mock;
  };
  let mockLogger: Logger;
  let mockPrice: CheckoutPrice;
 
  beforeEach(() => {
    mockCheckoutService = {
      extractCheckoutInfo: vi.fn(),
      proceedWithCheckout: vi.fn(),
    };

    mockConfirmationPort = {
      requestCheckoutConfirmation: vi.fn(),
    };

    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      error: vi.fn(),
      warn: vi.fn(),
      fatal: vi.fn(),
      child: vi.fn(() => mockLogger),
      flush: vi.fn(),
    } as Logger;

    mockPrice = {
      getAmount: vi.fn(() => 0.50),
      toDisplayString: vi.fn(() => '$0.50'),
      isZero: vi.fn(() => false),
    } as unknown as CheckoutPrice;
  });

  describe('Success flow', () => {
    it('should extract price, get user confirmation, and proceed with checkout', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(mockCheckoutService.extractCheckoutInfo).toHaveBeenCalledTimes(1);
      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(1);
      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
        expect.objectContaining({ price: mockPrice })
      );
      expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(1);
    });

    it('should include price in confirmation message', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));

      await useCase.execute();

      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
        expect.objectContaining({ price: mockPrice })
      );
    });
  });

  describe('User cancellation', () => {
    it('should abort checkout when user cancels confirmation', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('cancelled'))
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(result.unwrapErr().message).toMatch(/cancel/i);
      expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
    });

    it('should not proceed with checkout after cancellation', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('cancelled'))
      );

      await useCase.execute();

      expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(0);
    });
  });

  describe('Insufficient funds detection', () => {
    it('should return error when checkout state is INSUFFICIENT_FUNDS', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.insufficientFunds(),
          buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
        })
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(result.unwrapErr().message).toMatch(/insufficient.*funds/i);
      expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
      expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
    });

    it('should not ask for confirmation when funds are insufficient', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.insufficientFunds(),
          buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
        })
      );

      await useCase.execute();

      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(0);
    });
  });

  describe('Price extraction failure', () => {
    it('should return error when price cannot be extracted', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: null,
          state: CheckoutState.unknown(),
          buttonHtml: '',
        })
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(result.unwrapErr().message).toMatch(/extract|price|not found/i);
      expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
      expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
    });

    it('should return error when extraction service fails', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.err('Button not found')
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
    });
  });

  describe('Zero price warning', () => {
    it('should still require confirmation for $0.00 price', async () => {
      const zeroPriceMock = {
        getAmount: vi.fn(() => 0.00),
        toDisplayString: vi.fn(() => '$0.00'),
        isZero: vi.fn(() => true),
      } as unknown as CheckoutPrice;

      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: zeroPriceMock,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.00</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledTimes(1);
      expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
        expect.objectContaining({ price: zeroPriceMock })
      );
    });

    it('should proceed with checkout for zero price after confirmation', async () => {
      const zeroPriceMock = {
        getAmount: vi.fn(() => 0.00),
        toDisplayString: vi.fn(() => '$0.00'),
        isZero: vi.fn(() => true),
      } as unknown as CheckoutPrice;

      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: zeroPriceMock,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.00</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));

      await useCase.execute();

      expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalledTimes(1);
    });
  });

  describe('Checkout execution failure', () => {
    it('should return error when proceedWithCheckout fails', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(
        Result.err('Network error')
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(result.unwrapErr()).toContain('Network error');
    });
  });

  describe('BDD Scenarios', () => {
    it('Given checkout price $0.50 and READY state, When user confirms, Then checkout proceeds', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('confirmed'))
      );
      mockCheckoutService.proceedWithCheckout.mockResolvedValue(Result.ok(undefined));

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
    });

    it('Given checkout price $0.50, When user cancels, Then checkout is aborted', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.ready(),
          buttonHtml: '<a class="btn btn-success"><span>$0.50</span></a>',
        })
      );
      mockConfirmationPort.requestCheckoutConfirmation.mockResolvedValue(
        Result.ok(CheckoutConfirmation.create('cancelled'))
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
    });

    it('Given INSUFFICIENT_FUNDS state, When executing, Then error is returned', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.ok({
          price: mockPrice,
          state: CheckoutState.insufficientFunds(),
          buttonHtml: '<a class="btn btn-default"><span>$0.50</span></a>',
        })
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
    });

    it('Given price extraction failure, When executing, Then error is returned', async () => {
      const useCase = new ConfirmCheckoutUseCase(
        mockCheckoutService as unknown as CheckoutServicePort,
        mockConfirmationPort as unknown as CheckoutConfirmationPort,
        mockLogger
      );

      mockCheckoutService.extractCheckoutInfo.mockResolvedValue(
        Result.err('Button not found')
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
    });
  });
});