import { PLATFORM_ID, TransferState } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TimeoutError } from 'rxjs';

import { environment } from '../../../../environments/environment';
import { BOOTSTRAP_REQUEST_TIMEOUT_MS } from '../bootstrap-request-timeout';
import { AuthService } from './auth.service';
import { CookieService } from '../cookie/cookie.service';
import { TenantService } from '../tenant.service';

describe('AuthService', () => {
  let cookieStore: Record<string, string> = {};

  beforeEach(() => {
    cookieStore = {};
    TestBed.resetTestingModule();
  });

  function createCookieServiceStub() {
    return {
      get: (name: string) => cookieStore[name] || null,
      set: (name: string, value: string) => {
        cookieStore[name] = value;
      },
      delete: (name: string) => {
        delete cookieStore[name];
      }
    };
  }

  it('stores token and user on successful login', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        {
          provide: TenantService,
          useValue: { getTenant: () => ({ codigo: 'tenant-test' }) }
        },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service.login({ email: 'ada@example.com', password: 'secret123' }).subscribe((user) => {
      expect(user.email).toBe('ada@example.com');
    });

    const request = httpController.expectOne(`${environment.url}login`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual({
      email: 'ada@example.com',
      password: 'secret123',
      tenant_codigo: 'tenant-test'
    });
    expect(request.request.withCredentials).toBe(true);
    request.flush({
      message: 'Sesion iniciada correctamente.',
      token: 'plain-text-token',
      token_type: 'Bearer',
      user: {
        id: 1,
        nombre_apellido: 'Ada Lovelace',
        email: 'ada@example.com'
      }
    });

    expect(service.token()).toBe('plain-text-token');
    expect(service.user()?.email).toBe('ada@example.com');
    expect(service.isAuthenticated()).toBe(true);
    expect(cookieStore['shopit.front.auth.token']).toBe('plain-text-token');

    httpController.verify();
  });

  it('hydrates token from cookies and loads the current user during bootstrap', async () => {
    cookieStore['shopit.front.auth.token'] = 'persisted-token';

    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    const bootstrapPromise = service.bootstrap();
    const request = httpController.expectOne(`${environment.url}me`);

    expect(request.request.method).toBe('GET');
    request.flush({
      id: 9,
      nombre_apellido: 'Grace Hopper',
      email: 'grace@example.com'
    });

    await expect(bootstrapPromise).resolves.toBeUndefined();
    expect(service.user()?.email).toBe('grace@example.com');
    expect(service.isAuthenticated()).toBe(true);

    httpController.verify();
  });

  it('sends tenant and credentials when completing Google login', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        {
          provide: TenantService,
          useValue: { getTenant: () => ({ codigo: 'tenant-test' }) }
        },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service.completeGoogleLogin('oauth-code').subscribe();

    const request = httpController.expectOne(`${environment.url}auth/google/exchange`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual({
      oauth_code: 'oauth-code',
      tenant_codigo: 'tenant-test'
    });
    expect(request.request.withCredentials).toBe(true);
    request.flush({
      message: 'Sesion iniciada correctamente.',
      token: 'google-token',
      token_type: 'Bearer',
      user: {
        id: 1,
        nombre_apellido: 'Ada Lovelace',
        email: 'ada@example.com'
      }
    });

    httpController.verify();
  });

  it('clears an expired session when /me returns 401', async () => {
    cookieStore['shopit.front.auth.token'] = 'expired-token';

    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    const bootstrapPromise = service.bootstrap();
    const request = httpController.expectOne(`${environment.url}me`);

    request.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' });

    await expect(bootstrapPromise).resolves.toBeUndefined();
    expect(service.user()).toBeNull();
    expect(service.token()).toBeNull();
    expect(service.isAuthenticated()).toBe(false);
    expect(cookieStore['shopit.front.auth.token']).toBeUndefined();

    httpController.verify();
  });

  it('propagates a /me timeout without clearing the local session', async () => {
    cookieStore['shopit.front.auth.token'] = 'valid-token';
    vi.useFakeTimers();

    try {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(),
          provideHttpClientTesting(),
          AuthService,
          { provide: CookieService, useValue: createCookieServiceStub() },
          { provide: BOOTSTRAP_REQUEST_TIMEOUT_MS, useValue: 25 },
          TransferState,
        ],
      });

      const service = TestBed.inject(AuthService);
      const httpController = TestBed.inject(HttpTestingController);

      const bootstrapPromise = service.bootstrap();
      const rejection = expect(bootstrapPromise).rejects.toBeInstanceOf(TimeoutError);
      const request = httpController.expectOne(`${environment.url}me`);

      await vi.advanceTimersByTimeAsync(25);
      await rejection;

      expect(request.cancelled).toBe(true);
      expect(service.user()).toBeNull();
      expect(service.token()).toBe('valid-token');
      expect(cookieStore['shopit.front.auth.token']).toBe('valid-token');

      httpController.verify();
    } finally {
      vi.useRealTimers();
    }
  });

  it('registers without creating a session', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        {
          provide: TenantService,
          useValue: { getTenant: () => ({ codigo: 'tenant-test' }) }
        },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service
      .register({
        nombre_apellido: 'Ada Lovelace',
        email: 'ada@example.com',
        password: 'secret123',
        password_confirmation: 'secret123'
      })
      .subscribe((response) => {
        expect(response.data.email).toBe('ada@example.com');
      });

    const request = httpController.expectOne(`${environment.url}register`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body.tenant_codigo).toBe('tenant-test');
    request.flush({
      message: 'Usuario registrado correctamente.',
      data: {
        id: 1,
        nombre_apellido: 'Ada Lovelace',
        email: 'ada@example.com'
      }
    });

    expect(service.token()).toBeNull();
    expect(service.user()).toBeNull();
    expect(service.isAuthenticated()).toBe(false);

    httpController.verify();
  });

  it('requests a password reset and exposes the final HTTP status', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        {
          provide: TenantService,
          useValue: { getTenant: () => ({ codigo: 'tenant-test' }) }
        },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service.requestPasswordReset('ada@example.com').subscribe((response) => {
      expect(response.status).toBe(202);
      expect(response.body?.status).toBe('pending');
    });

    const request = httpController.expectOne(`${environment.url}password/reset-attempts`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual({
      email: 'ada@example.com',
      tenant_codigo: 'tenant-test'
    });
    request.flush(
      {
        message: 'Si el email está registrado, recibirás un código.',
        status: 'pending'
      },
      { status: 202, statusText: 'Accepted' }
    );

    httpController.verify();
  });

  it('validates a password reset code and exposes its final status', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service.validatePasswordResetCode('ada@example.com', '0123').subscribe((response) => {
      expect(response.status).toBe(200);
      expect(response.body?.status).toBe('validated');
    });

    const request = httpController.expectOne(`${environment.url}password/reset-attempts/validate`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual({
      email: 'ada@example.com',
      codigo: '0123'
    });
    request.flush(
      {
        message: 'Código validado correctamente.',
        status: 'validated'
      },
      { status: 200, statusText: 'OK' }
    );

    httpController.verify();
  });

  it('resets the password and exposes the consumed attempt status', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);
    const payload = {
      email: 'ada@example.com',
      codigo: '0123',
      password: 'NewSecret!456',
      password_confirmation: 'NewSecret!456'
    };

    service.resetPassword(payload).subscribe((response) => {
      expect(response.status).toBe(200);
      expect(response.body?.status).toBe('used');
    });

    const request = httpController.expectOne(`${environment.url}password/reset`);
    expect(request.request.method).toBe('POST');
    expect(request.request.body).toEqual(payload);
    request.flush(
      {
        message: 'Contraseña modificada correctamente.',
        status: 'used'
      },
      { status: 200, statusText: 'OK' }
    );

    httpController.verify();
  });

  it('propagates logout errors without clearing the local session', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        AuthService,
        { provide: CookieService, useValue: createCookieServiceStub() },
        {
          provide: TenantService,
          useValue: { getTenant: () => ({ codigo: 'tenant-test' }) }
        },
        TransferState
      ]
    });

    const service = TestBed.inject(AuthService);
    const httpController = TestBed.inject(HttpTestingController);

    service.login({ email: 'ada@example.com', password: 'secret123' }).subscribe();
    httpController.expectOne(`${environment.url}login`).flush({
      message: 'Sesion iniciada correctamente.',
      token: 'plain-text-token',
      token_type: 'Bearer',
      user: {
        id: 1,
        nombre_apellido: 'Ada Lovelace',
        email: 'ada@example.com'
      }
    });

    service.logout().subscribe({
      error: () => undefined
    });

    const logoutRequest = httpController.expectOne(`${environment.url}logout`);
    expect(logoutRequest.request.method).toBe('POST');
    logoutRequest.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' });

    expect(service.token()).toBe('plain-text-token');
    expect(service.user()?.email).toBe('ada@example.com');
    expect(cookieStore['shopit.front.auth.token']).toBe('plain-text-token');

    httpController.verify();
  });
});
