import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Result } from '@gridpilot/shared/application/Result';
import { CheckoutPriceExtractor } from '../../../apps/companion/main/automation/infrastructure/automation/CheckoutPriceExtractor';
import { CheckoutStateEnum } from 'apps/companion/main/automation/domain/value-objects/CheckoutState';

/**
 * CheckoutPriceExtractor Integration Tests - GREEN PHASE
 *
 * Tests verify HTML parsing for checkout price extraction and state detection.
 */

type Page = ConstructorParameters<typeof CheckoutPriceExtractor>[0];
type Locator = ReturnType<Page['locator']>;

describe('CheckoutPriceExtractor Integration', () => {
  let mockPage: Page;
  let mockLocator: any;
  let mockPillLocator: any;

  beforeEach(() => {
    // Create nested locator mock for span.label-pill
    mockPillLocator = {
      textContent: vi.fn().mockResolvedValue('$0.50'),
      first: vi.fn().mockReturnThis(),
      locator: vi.fn().mockReturnThis(),
    };

    mockLocator = {
      getAttribute: vi.fn(),
      innerHTML: vi.fn(),
      textContent: vi.fn(),
      locator: vi.fn(() => mockPillLocator),
      first: vi.fn().mockReturnThis(),
    };

    mockPage = {
      locator: vi.fn((selector) => {
        if (selector === '.label-pill, .label-inverse') {
          return mockPillLocator;
        }
        return mockLocator;
      }),
    };
  });

  describe('Success state HTML extraction', () => {
    it('should extract $0.50 from success button', async () => {
      const buttonHtml = '<a class="btn btn-success"><span class="label label-pill label-inverse">$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).not.toBeNull();
      expect(info.price!.getAmount()).toBe(0.50);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });

    it('should extract $5.00 from success button', async () => {
      const buttonHtml = '<a class="btn btn-success"><span class="label label-pill label-inverse">$5.00</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$5.00');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price!.getAmount()).toBe(5.00);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });

    it('should extract $100.00 from success button', async () => {
      const buttonHtml = '<a class="btn btn-success"><span class="label label-pill label-inverse">$100.00</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$100.00');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price!.getAmount()).toBe(100.00);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });

    it('should detect READY state from btn-success class', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().state.getValue()).toBe(CheckoutStateEnum.READY);
    });
  });

  describe('Insufficient funds HTML detection', () => {
    it('should detect INSUFFICIENT_FUNDS when btn-success is missing', async () => {
      const buttonHtml = '<a class="btn btn-default"><span class="label label-pill label-inverse">$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-default');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).not.toBeNull();
      expect(info.price!.getAmount()).toBe(0.50);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.INSUFFICIENT_FUNDS);
    });

    it('should still extract price when funds are insufficient', async () => {
      const buttonHtml = '<a class="btn btn-default"><span>$10.00</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-default');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$10.00');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price!.getAmount()).toBe(10.00);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.INSUFFICIENT_FUNDS);
    });

    it('should detect btn-primary as insufficient funds', async () => {
      const buttonHtml = '<a class="btn btn-primary"><span>$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-primary');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().state.getValue()).toBe(CheckoutStateEnum.INSUFFICIENT_FUNDS);
    });
  });

  describe('Price parsing variations', () => {
    it('should parse price with nested span tags', async () => {
      const buttonHtml = '<a class="btn btn-success"><span class="outer"><span class="inner">$0.50</span></span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price!.getAmount()).toBe(0.50);
    });

    it('should parse price with whitespace', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>  $0.50  </span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('  $0.50  ');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price!.getAmount()).toBe(0.50);
    });

    it('should parse price with multiple classes', async () => {
      const buttonHtml = '<a class="btn btn-lg btn-success pull-right"><span class="label label-pill label-inverse">$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-lg btn-success pull-right');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price!.getAmount()).toBe(0.50);
      expect(result.unwrap().state.getValue()).toBe(CheckoutStateEnum.READY);
    });
  });

  describe('Missing button handling', () => {
    it('should return UNKNOWN state when button not found', async () => {
      mockLocator.getAttribute.mockResolvedValue(null);
      mockLocator.innerHTML.mockRejectedValue(new Error('Element not found'));

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).toBeNull();
      expect(info.state.getValue()).toBe(CheckoutStateEnum.UNKNOWN);
    });

    it('should return null price when button not found', async () => {
      mockLocator.getAttribute.mockResolvedValue(null);
      mockPillLocator.textContent.mockResolvedValue(null);

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price).toBeNull();
    });
  });

  describe('Malformed HTML handling', () => {
    it('should return null price when price text is invalid', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>Invalid Price</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('Invalid Price');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).toBeNull();
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });

    it('should return null price when price is missing dollar sign', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price).toBeNull();
    });

    it('should handle empty price text', async () => {
      const buttonHtml = '<a class="btn btn-success"><span></span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().price).toBeNull();
    });
  });

  describe('Button HTML capture', () => {
    it('should capture full button HTML for debugging', async () => {
      const buttonHtml = '<a class="btn btn-success"><span class="label label-pill label-inverse">$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

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

    it('should capture button HTML even when price parsing fails', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>Invalid</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('Invalid');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

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

    it('should return empty buttonHtml when button not found', async () => {
      mockLocator.getAttribute.mockResolvedValue(null);
      mockLocator.innerHTML.mockResolvedValue('');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().buttonHtml).toBe('');
    });
  });

  describe('BDD Scenarios', () => {
    it('Given checkout button with $0.50 and btn-success, When extracting, Then price is $0.50 and state is READY', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price!.getAmount()).toBe(0.50);
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });

    it('Given checkout button with $0.50 without btn-success, When extracting, Then state is INSUFFICIENT_FUNDS', async () => {
      const buttonHtml = '<a class="btn btn-default"><span>$0.50</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-default');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('$0.50');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().state.getValue()).toBe(CheckoutStateEnum.INSUFFICIENT_FUNDS);
    });

    it('Given button not found, When extracting, Then state is UNKNOWN and price is null', async () => {
      mockLocator.getAttribute.mockResolvedValue(null);
      mockLocator.innerHTML.mockResolvedValue('');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).toBeNull();
      expect(info.state.getValue()).toBe(CheckoutStateEnum.UNKNOWN);
    });

    it('Given malformed price text, When extracting, Then price is null but state is detected', async () => {
      const buttonHtml = '<a class="btn btn-success"><span>Invalid</span></a>';
      
      mockLocator.getAttribute.mockResolvedValue('btn btn-success');
      mockLocator.innerHTML.mockResolvedValue(buttonHtml);
      mockPillLocator.textContent.mockResolvedValue('Invalid');

      const extractor = new CheckoutPriceExtractor(mockPage);
      const result = await extractor.extractCheckoutInfo();

      expect(result.isOk()).toBe(true);
      const info = result.unwrap();
      expect(info.price).toBeNull();
      expect(info.state.getValue()).toBe(CheckoutStateEnum.READY);
    });
  });
});