import { describe, it, beforeEach, expect, vi } from 'vitest';
import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { PositiveIntegerInputDirective } from './positive-integer-input.directive';

/**
 * Test host component to test the directive in a realistic scenario
 */
@Component({
  selector: 'test-host',
  standalone: true,
  imports: [FormsModule, PositiveIntegerInputDirective],
  template: `
    <input
      type="number"
      positiveIntegerInput
      [(ngModel)]="value"
      data-testid="test-input"
    />
  `,
})
class TestHostComponent {
  value = signal<number | undefined>(undefined);
}

describe('PositiveIntegerInputDirective', () => {
  let fixture: ComponentFixture<TestHostComponent>;
  let component: TestHostComponent;
  let inputElement: HTMLInputElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [TestHostComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    inputElement = fixture.nativeElement.querySelector(
      '[data-testid="test-input"]',
    ) as HTMLInputElement;
  });

  describe('Component Setup and Initialization', () => {
    it('should create', () => {
      expect(component).toBeTruthy();
    });

    it('should attach directive to input element', () => {
      expect(inputElement).toBeTruthy();
      expect(inputElement.type).toBe('number');
    });
  });

  describe('Keyboard Input (keydown) - Blocked Characters', () => {
    it('should prevent decimal point (.) from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: '.' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent comma (,) from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: ',' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent minus sign (-) from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: '-' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent plus sign (+) from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: '+' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent lowercase e from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: 'e' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent uppercase E from being entered', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: 'E' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).toHaveBeenCalled();
    });
  });

  describe('Keyboard Input (keydown) - Allowed Characters', () => {
    it('should allow digit keys (0-9)', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: '5' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).not.toHaveBeenCalled();
    });

    it('should allow backspace key', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: 'Backspace' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).not.toHaveBeenCalled();
    });

    it('should allow delete key', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: 'Delete' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).not.toHaveBeenCalled();
    });

    it('should allow arrow keys', () => {
      // Arrange
      const leftEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft' });
      const rightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight' });
      const preventDefaultSpyLeft = vi.spyOn(leftEvent, 'preventDefault');
      const preventDefaultSpyRight = vi.spyOn(rightEvent, 'preventDefault');

      // Act
      inputElement.dispatchEvent(leftEvent);
      inputElement.dispatchEvent(rightEvent);

      // Assert
      expect(preventDefaultSpyLeft).not.toHaveBeenCalled();
      expect(preventDefaultSpyRight).not.toHaveBeenCalled();
    });

    it('should allow tab key', () => {
      // Arrange
      const event = new KeyboardEvent('keydown', { key: 'Tab' });
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      // Act
      inputElement.dispatchEvent(event);

      // Assert
      expect(preventDefaultSpy).not.toHaveBeenCalled();
    });
  });

  describe('Input Sanitization - Simulating Paste/External Input', () => {
    it('should sanitize negative number (-100 → 100)', () => {
      // Arrange
      inputElement.value = '-100';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('100');
    });

    it('should sanitize decimal with dot (1.000 → 1000)', () => {
      // Arrange
      inputElement.value = '1.000';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('1000');
    });

    it('should sanitize scientific notation as typed (3142 stays 3142)', () => {
      // Note: type="number" inputs accept "3.14e2" but it becomes "3142"
      // before our directive sees it
      // Arrange
      inputElement.value = '3142';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('3142');
    });

    it('should remove leading zeros (00123 → 123)', () => {
      // Arrange
      inputElement.value = '00123';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('123');
    });

    it('should remove leading zeros (007 → 7)', () => {
      // Arrange
      inputElement.value = '007';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('7');
    });

    it('should handle single zero (0 → empty)', () => {
      // Arrange
      inputElement.value = '0';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('');
    });

    it('should handle multiple zeros (000 → empty)', () => {
      // Arrange
      inputElement.value = '000';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('');
    });

    it('should handle valid positive integer without changes (123 → 123)', () => {
      // Arrange
      inputElement.value = '123';
      const dispatchEventSpy = vi.spyOn(inputElement, 'dispatchEvent');

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('123');
      // Should only have the initial dispatchEvent call, not an additional one
      expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
    });

    it('should sanitize number with plus at start (+42 becomes 42 by browser)', () => {
      // Note: type="number" inputs convert "+42" to "42" before our directive sees it
      // Arrange
      inputElement.value = '42';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('42');
    });
  });

  describe('Input Events - General Sanitization', () => {
    it('should sanitize programmatically set invalid value', () => {
      // Arrange
      inputElement.value = '-50';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('50');
    });

    it('should sanitize value with decimal point', () => {
      // Arrange
      inputElement.value = '12.34';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('1234');
    });

    it('should remove leading zeros on input', () => {
      // Arrange
      inputElement.value = '00789';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('789');
    });

    it('should not trigger unnecessary updates for valid input', () => {
      // Arrange
      inputElement.value = '123';
      const dispatchEventSpy = vi.spyOn(inputElement, 'dispatchEvent');

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      // Should only have the initial dispatchEvent call, not an additional one
      expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
    });
  });

  describe('Integration with Angular Forms', () => {
    it('should update ngModel with sanitized value', async () => {
      // Arrange
      inputElement.value = '-250';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));
      await fixture.whenStable();
      fixture.detectChanges();

      // Assert
      expect(inputElement.value).toBe('250');
    });

    it('should handle valid positive integer input', async () => {
      // Arrange
      inputElement.value = '42';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));
      await fixture.whenStable();

      // Assert
      expect(inputElement.value).toBe('42');
    });
  });

  describe('Edge Cases', () => {
    it('should handle very large numbers', () => {
      // Arrange
      const largeNumber = '999999999999';
      inputElement.value = largeNumber;

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe(largeNumber);
    });

    it('should handle decimal number by removing decimal point (12.34 → 1234)', () => {
      // Arrange
      inputElement.value = '12.34';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('1234');
    });

    it('should handle negative with decimals (-12.34 → 1234)', () => {
      // Arrange
      inputElement.value = '-12.34';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('1234');
    });

    it('should handle leading zeros with decimals (00.789 → 789)', () => {
      // Arrange
      inputElement.value = '00.789';

      // Act
      inputElement.dispatchEvent(new Event('input', { bubbles: true }));

      // Assert
      expect(inputElement.value).toBe('789');
    });
  });
});
