import { HttpResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap, provideRouter, Router } from '@angular/router';
import { of, throwError } from 'rxjs';

import { AuthService } from '../../../../core/services/auth/auth.service';
import { ToastService } from '../../../../core/services/toast.service';
import { RecoverPasswordCodePageComponent } from './recover-password-code-page.component';

describe('RecoverPasswordCodePageComponent', () => {
  let validatePasswordResetCode: ReturnType<typeof vi.fn>;
  let requestPasswordReset: ReturnType<typeof vi.fn>;
  let showDangerToast: ReturnType<typeof vi.fn>;
  let showSuccessToast: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    TestBed.resetTestingModule();
    vi.spyOn(console, 'error').mockImplementation(() => undefined);
    vi.spyOn(console, 'warn').mockImplementation(() => undefined);
    validatePasswordResetCode = vi.fn(() => of(new HttpResponse({
      body: {
        message: 'Código validado correctamente.',
        status: 'validated' as const,
      },
      status: 200,
    })));
    requestPasswordReset = vi.fn(() => of(new HttpResponse({
      body: {
        message: 'Solicitud aceptada.',
        status: 'pending' as const,
      },
      status: 202,
    })));
    showDangerToast = vi.fn();
    showSuccessToast = vi.fn();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  function authProviders() {
    return [
      {
        provide: AuthService,
        useValue: { requestPasswordReset, validatePasswordResetCode },
      },
      {
        provide: ToastService,
        useValue: { danger: showDangerToast, success: showSuccessToast },
      },
    ];
  }

  it('shows the email received from the recovery flow', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).toContain('ada@example.com');
  });

  it('moves focus to the next input after entering a digit', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    fixture.detectChanges();
    const inputs = fixture.nativeElement.querySelectorAll('input');

    inputs[0].focus();
    component.updateDigit('digit1', '1');

    expect(document.activeElement).toBe(inputs[1]);
  });

  it('distributes a pasted four-digit code across all inputs', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    fixture.detectChanges();
    const inputs = fixture.nativeElement.querySelectorAll('input');
    const pasteEvent = new Event('paste', {
      bubbles: true,
      cancelable: true,
    });
    Object.defineProperty(pasteEvent, 'clipboardData', {
      value: {
        getData: () => '01 23',
      },
    });

    inputs[0].dispatchEvent(pasteEvent);
    fixture.detectChanges();

    expect(pasteEvent.defaultPrevented).toBe(true);
    expect(component.form.getRawValue()).toEqual({
      digit1: '0',
      digit2: '1',
      digit3: '2',
      digit4: '3',
    });
    expect(component.form.valid).toBe(true);
    expect(document.activeElement).toBe(inputs[3]);
  });

  it('accepts only a complete four-digit code and navigates back preserving the email', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    const router = TestBed.inject(Router);
    const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);

    component.updateDigit('digit1', '1');
    component.updateDigit('digit2', 'a2');
    component.updateDigit('digit3', '3');
    component.updateDigit('digit4', '4');

    expect(component.form.getRawValue()).toEqual({
      digit1: '1',
      digit2: '2',
      digit3: '3',
      digit4: '4',
    });
    expect(component.form.valid).toBe(true);

    component.goBack();

    expect(navigateSpy).toHaveBeenCalledWith(['/recuperar-contrasena'], {
      queryParams: { email: 'ada@example.com' },
    });
  });

  it('continues to the reset password page with the email and code', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    const router = TestBed.inject(Router);
    const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);

    component.form.setValue({
      digit1: '1',
      digit2: '2',
      digit3: '3',
      digit4: '4',
    });
    component.onSubmit();

    expect(validatePasswordResetCode).toHaveBeenCalledWith('ada@example.com', '1234');
    expect(navigateSpy).toHaveBeenCalledWith(['/recuperar-contrasena/restablecer'], {
      queryParams: {
        email: 'ada@example.com',
        code: '1234',
      },
    });
  });

  it('shows the validation error and stays on the code page', async () => {
    validatePasswordResetCode.mockReturnValue(throwError(() => ({
      error: {
        errors: {
          codigo: ['El código ingresado es inválido.'],
        },
      },
    })));

    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    const router = TestBed.inject(Router);
    const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);

    component.form.setValue({
      digit1: '9',
      digit2: '9',
      digit3: '9',
      digit4: '9',
    });
    component.onSubmit();

    expect(navigateSpy).not.toHaveBeenCalled();
    expect(component.serverError()).toBe('El código ingresado es inválido.');
    expect(showDangerToast).toHaveBeenCalledWith('El código ingresado es inválido.');
  });

  it('requests a new code and clears the previous digits', async () => {
    await TestBed.configureTestingModule({
      imports: [RecoverPasswordCodePageComponent],
      providers: [
        provideRouter([]),
        ...authProviders(),
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              queryParamMap: convertToParamMap({ email: 'ada@example.com' }),
            },
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(RecoverPasswordCodePageComponent);
    const component = fixture.componentInstance as any;
    fixture.detectChanges();
    component.form.setValue({
      digit1: '1',
      digit2: '2',
      digit3: '3',
      digit4: '4',
    });

    component.resendCode();

    expect(requestPasswordReset).toHaveBeenCalledWith('ada@example.com');
    expect(component.form.getRawValue()).toEqual({
      digit1: '',
      digit2: '',
      digit3: '',
      digit4: '',
    });
    expect(showSuccessToast).toHaveBeenCalledWith('Código reenviado correctamente.');
  });
});
