import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FormFieldWrapperComponent } from './form-field-wrapper';

@Component({
  template: `
    <ccl-form-field-wrapper 
      [label]="label" 
      [required]="required"
      [hint]="hint"
      [error]="error"
      [state]="state"
      [for]="forId">
      <input #input [id]="forId" type="text" />
    </ccl-form-field-wrapper>
  `,
  standalone: true,
  imports: [FormFieldWrapperComponent]
})
class TestHostComponent {
  label = 'Email';
  required = false;
  hint = '';
  error = '';
  state: 'default' | 'error' | 'success' | 'disabled' = 'default';
  forId = 'test-input';
}

describe('FormFieldWrapperComponent', () => {
  let component: FormFieldWrapperComponent;
  let fixture: ComponentFixture<FormFieldWrapperComponent>;
  let hostComponent: TestHostComponent;
  let hostFixture: ComponentFixture<TestHostComponent>;

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

    fixture = TestBed.createComponent(FormFieldWrapperComponent);
    component = fixture.componentInstance;
    
    hostFixture = TestBed.createComponent(TestHostComponent);
    hostComponent = hostFixture.componentInstance;
    
    fixture.detectChanges();
    hostFixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  describe('Input Properties', () => {
    it('should have default values', () => {
      expect(component.label).toBe('');
      expect(component.required).toBe(false);
      expect(component.hint).toBe('');
      expect(component.error).toBe('');
      expect(component.state).toBe('default');
      expect(component.for).toBe('');
    });

    it('should accept label input', () => {
      component.label = 'Email Address';
      fixture.detectChanges();
      expect(component.label).toBe('Email Address');
    });

    it('should accept required input', () => {
      component.required = true;
      fixture.detectChanges();
      expect(component.required).toBe(true);
    });

    it('should accept hint input', () => {
      component.hint = 'We\'ll never share your email';
      fixture.detectChanges();
      expect(component.hint).toBe('We\'ll never share your email');
    });

    it('should accept error input', () => {
      component.error = 'Invalid email address';
      fixture.detectChanges();
      expect(component.error).toBe('Invalid email address');
    });

    it('should accept state input', () => {
      component.state = 'error';
      fixture.detectChanges();
      expect(component.state).toBe('error');
    });

    it('should accept for input', () => {
      component.for = 'custom-input-id';
      fixture.detectChanges();
      expect(component.for).toBe('custom-input-id');
    });
  });

  describe('ID Generation and Management', () => {
    it('should generate unique field ID when for is not provided', () => {
      expect(component.fieldId).toMatch(/^ccl-form-field-/);
      expect(component.fieldId.length).toBeGreaterThan(15);
    });

    it('should use provided for as field ID', () => {
      component.for = 'custom-input-id';
      fixture.detectChanges();
      expect(component.fieldId).toBe('custom-input-id');
    });

    it('should generate unique hint ID', () => {
      component.for = 'test-input';
      fixture.detectChanges();
      expect(component.hintId).toBe('test-input-hint');
    });

    it('should generate unique error ID', () => {
      component.for = 'test-input';
      fixture.detectChanges();
      expect(component.errorId).toBe('test-input-error');
    });

    it('should have different IDs for different instances', () => {
      const fixture2 = TestBed.createComponent(FormFieldWrapperComponent);
      const component2 = fixture2.componentInstance;
      expect(component.fieldId).not.toBe(component2.fieldId);
    });
  });

  describe('State Management', () => {
    it('should detect error state from error property', () => {
      component.error = 'Some error';
      fixture.detectChanges();
      expect(component.isErrorState).toBe(true);
    });

    it('should detect error state from state property', () => {
      component.state = 'error';
      fixture.detectChanges();
      expect(component.isErrorState).toBe(true);
    });

    it('should detect success state when not in error state', () => {
      component.state = 'success';
      component.error = '';
      fixture.detectChanges();
      expect(component.isSuccessState).toBe(true);
    });

    it('should not detect success state when in error state', () => {
      component.state = 'success';
      component.error = 'Some error';
      fixture.detectChanges();
      expect(component.isSuccessState).toBe(false);
    });

    it('should detect disabled state', () => {
      component.state = 'disabled';
      fixture.detectChanges();
      expect(component.isDisabledState).toBe(true);
    });
  });

  describe('ARIA Described By', () => {
    it('should include hint ID when hint is provided', () => {
      component.for = 'test-input';
      component.hint = 'Helper text';
      fixture.detectChanges();
      expect(component.describedBy).toBe('test-input-hint');
    });

    it('should include error ID when error is provided', () => {
      component.for = 'test-input';
      component.error = 'Error text';
      fixture.detectChanges();
      expect(component.describedBy).toBe('test-input-error');
    });

    it('should include both hint and error IDs when both are provided', () => {
      component.for = 'test-input';
      component.hint = 'Helper text';
      component.error = 'Error text';
      fixture.detectChanges();
      expect(component.describedBy).toBe('test-input-hint test-input-error');
    });

    it('should be empty when neither hint nor error are provided', () => {
      component.for = 'test-input';
      fixture.detectChanges();
      expect(component.describedBy).toBe('');
    });
  });

  describe('Template Rendering', () => {
    it('should render wrapper element', () => {
      const wrapper = fixture.debugElement.query(By.css('.ccl-form-field-wrapper'));
      expect(wrapper).toBeTruthy();
    });

    it('should render label when provided', () => {
      component.label = 'Email';
      fixture.detectChanges();
      const label = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__label'));
      expect(label.nativeElement.textContent).toContain('Email');
    });

    it('should not render label when not provided', () => {
      component.label = '';
      fixture.detectChanges();
      const label = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__label'));
      expect(label).toBeNull();
    });

    it('should render required asterisk when required=true', () => {
      component.label = 'Email';
      component.required = true;
      fixture.detectChanges();
      const required = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__required'));
      expect(required.nativeElement.textContent).toBe('*');
    });

    it('should not render required asterisk when required=false', () => {
      component.label = 'Email';
      component.required = false;
      fixture.detectChanges();
      const required = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__required'));
      expect(required).toBeNull();
    });

    it('should render hint when provided and not in error state', () => {
      component.hint = 'Helper text';
      fixture.detectChanges();
      const hint = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__hint'));
      expect(hint.nativeElement.textContent).toContain('Helper text');
    });

    it('should not render hint when in error state', () => {
      component.hint = 'Helper text';
      component.error = 'Error text';
      fixture.detectChanges();
      const hint = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__hint'));
      expect(hint).toBeNull();
    });

    it('should render error when provided', () => {
      component.error = 'Error text';
      fixture.detectChanges();
      const error = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__error'));
      expect(error.nativeElement.textContent).toContain('Error text');
    });

    it('should not render error when not provided', () => {
      component.error = '';
      fixture.detectChanges();
      const error = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__error'));
      expect(error).toBeNull();
    });

    it('should render error when in error state even without error text', () => {
      component.state = 'error';
      fixture.detectChanges();
      const error = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__error'));
      expect(error).toBeTruthy();
    });
  });

  describe('CSS Classes', () => {
    it('should apply disabled class when in disabled state', () => {
      component.state = 'disabled';
      fixture.detectChanges();
      const wrapper = fixture.debugElement.query(By.css('.ccl-form-field-wrapper'));
      expect(wrapper.nativeElement.classList).toContain('ccl-form-field-wrapper--disabled');
    });

    it('should apply error class when in error state', () => {
      component.error = 'Error text';
      fixture.detectChanges();
      const wrapper = fixture.debugElement.query(By.css('.ccl-form-field-wrapper'));
      expect(wrapper.nativeElement.classList).toContain('ccl-form-field-wrapper--error');
    });

    it('should apply success class when in success state', () => {
      component.state = 'success';
      fixture.detectChanges();
      const wrapper = fixture.debugElement.query(By.css('.ccl-form-field-wrapper'));
      expect(wrapper.nativeElement.classList).toContain('ccl-form-field-wrapper--success');
    });

    it('should not apply success class when in error state', () => {
      component.state = 'success';
      component.error = 'Error text';
      fixture.detectChanges();
      const wrapper = fixture.debugElement.query(By.css('.ccl-form-field-wrapper'));
      expect(wrapper.nativeElement.classList).not.toContain('ccl-form-field-wrapper--success');
    });
  });

  describe('Accessibility', () => {
    it('should associate label with input via for attribute', () => {
      component.label = 'Email';
      component.for = 'test-input';
      fixture.detectChanges();
      const label = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__label'));
      expect(label.nativeElement.getAttribute('for')).toBe('test-input');
    });

    it('should set aria-describedby on input when hint is provided', async () => {
      hostComponent.hint = 'Helper text';
      hostFixture.detectChanges();
      await hostFixture.whenStable();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-describedby')).toBe('test-input-hint');
    });

    it('should set aria-describedby on input when error is provided', async () => {
      hostComponent.error = 'Error text';
      hostFixture.detectChanges();
      await hostFixture.whenStable();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-describedby')).toBe('test-input-error');
    });

    it('should set aria-describedby on input when both hint and error are provided', async () => {
      hostComponent.hint = 'Helper text';
      hostComponent.error = 'Error text';
      hostFixture.detectChanges();
      await hostFixture.whenStable();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-describedby')).toBe('test-input-hint test-input-error');
    });

    it('should set aria-required on input when required=true', async () => {
      hostComponent.required = true;
      hostFixture.detectChanges();
      await hostFixture.whenStable();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-required')).toBe('true');
    });

    it('should remove aria-required from input when required=false', () => {
      hostComponent.required = false;
      hostFixture.detectChanges();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-required')).toBeNull();
    });

    it('should set aria-invalid on input when in error state', async () => {
      hostComponent.error = 'Error text';
      hostFixture.detectChanges();
      await hostFixture.whenStable();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-invalid')).toBe('true');
    });

    it('should remove aria-invalid from input when not in error state', () => {
      hostComponent.error = '';
      hostFixture.detectChanges();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.getAttribute('aria-invalid')).toBeNull();
    });

    it('should add role="alert" to error message', () => {
      component.error = 'Error text';
      fixture.detectChanges();
      const error = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__error'));
      expect(error.nativeElement.getAttribute('role')).toBe('alert');
    });

    it('should add aria-label="required" to required asterisk', () => {
      component.label = 'Email';
      component.required = true;
      fixture.detectChanges();
      const required = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__required'));
      expect(required.nativeElement.getAttribute('aria-label')).toBe('required');
    });
  });

  describe('Content Projection', () => {
    it('should project content into input container', () => {
      hostFixture.detectChanges();
      const inputContainer = hostFixture.debugElement.query(By.css('.ccl-form-field-wrapper__input-container'));
      const input = inputContainer.query(By.css('input'));
      expect(input).toBeTruthy();
    });

    it('should set input ID when not already set', () => {
      hostFixture.detectChanges();
      const input = hostFixture.debugElement.query(By.css('input'));
      expect(input.nativeElement.id).toBe('test-input');
    });
  });

  describe('Error Priority', () => {
    it('should prioritize error over success state', () => {
      component.state = 'success';
      component.error = 'Error text';
      fixture.detectChanges();
      expect(component.isSuccessState).toBe(false);
      expect(component.isErrorState).toBe(true);
    });

    it('should show error message instead of hint when both are provided', () => {
      component.hint = 'Helper text';
      component.error = 'Error text';
      fixture.detectChanges();
      
      const hint = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__hint'));
      const error = fixture.debugElement.query(By.css('.ccl-form-field-wrapper__error'));
      
      expect(hint).toBeNull();
      expect(error.nativeElement.textContent).toContain('Error text');
    });
  });
});
