import { TestBed } from '@angular/core/testing';
import {
  HttpClientTestingModule,
  HttpTestingController
} from '@angular/common/http/testing';
// NOTE: UserInfo is the interface Task 2 will add to auth.service.ts.
// Importing it here intentionally causes a compile error until Task 2 is implemented.
// Task 2 MUST export `UserInfo` (not the old `User`) for these tests to pass.
import {
  AuthService,
  LoginResultType,
  UserInfo
} from './auth.service';
import { ApiConfigService } from '../../core/services/api-config.service';

const MOCK_USER: UserInfo = {
  id: 'user-123',
  email: 'test@example.com',
  roles: ['Admin'],
  languagePreference: 'en'
};

const MOCK_API_RESPONSE = {
  accessToken: 'mock-access-token',
  expiresIn: 900,
  user: MOCK_USER
};

describe('AuthService', () => {
  let service: AuthService;
  let httpMock: HttpTestingController;
  let apiConfig: ApiConfigService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [AuthService, ApiConfigService]
    });
    service = TestBed.inject(AuthService);
    httpMock = TestBed.inject(HttpTestingController);
    apiConfig = TestBed.inject(ApiConfigService);
  });

  afterEach(() => {
    httpMock.verify();
  });

  // ── login() ────────────────────────────────────────────────────────────────

  describe('login()', () => {
    it('should POST to /api/auth/login with email and password', () => {
      service.login({ email: 'test@example.com', password: 'secret' }).subscribe();
      const req = httpMock.expectOne(`${apiConfig.authUrl}/login`);
      expect(req.request.method).toBe('POST');
      expect(req.request.body).toEqual({ email: 'test@example.com', password: 'secret' });
      expect(req.request.withCredentials).toBeTrue();
      req.flush(MOCK_API_RESPONSE);
    });

    it('should return LoginResultType.Success and store token + user on 200', () => {
      let result: any;
      service.login({ email: 'test@example.com', password: 'secret' }).subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(MOCK_API_RESPONSE);

      expect(result.result).toBe(LoginResultType.Success);
      expect(result.responseData).toEqual(MOCK_USER);
      expect(service.getToken()).toBe('mock-access-token');
      expect(service.getCurrentUser()).toEqual(MOCK_USER);
      expect(service.isAuthenticated()).toBeTrue();
    });

    it('should return LoginResultType.InvalidCredentials on 401', () => {
      let result: any;
      service.login({ email: 'bad@example.com', password: 'wrong' }).subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(
        { message: 'Invalid credentials' },
        { status: 401, statusText: 'Unauthorized' }
      );

      expect(result.result).toBe(LoginResultType.InvalidCredentials);
      expect(service.getToken()).toBeNull();
      expect(service.isAuthenticated()).toBeFalse();
    });

    it('should return LoginResultType.Error on non-401 HTTP error', () => {
      let result: any;
      service.login({ email: 'test@example.com', password: 'secret' }).subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(
        { message: 'Server error' },
        { status: 500, statusText: 'Internal Server Error' }
      );

      expect(result.result).toBe(LoginResultType.Error);
    });
  });

  // ── refresh() ──────────────────────────────────────────────────────────────

  describe('refresh()', () => {
    it('should POST to /api/auth/refresh with withCredentials', () => {
      service.refresh().subscribe();
      const req = httpMock.expectOne(`${apiConfig.authUrl}/refresh`);
      expect(req.request.method).toBe('POST');
      expect(req.request.withCredentials).toBeTrue();
      req.flush(MOCK_API_RESPONSE);
    });

    it('should return true and update token + user on 200', () => {
      let result: boolean | undefined;
      service.refresh().subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(MOCK_API_RESPONSE);

      expect(result).toBeTrue();
      expect(service.getToken()).toBe('mock-access-token');
      expect(service.getCurrentUser()).toEqual(MOCK_USER);
    });

    it('should return false and leave state unchanged on 401', () => {
      let result: boolean | undefined;
      service.refresh().subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(
        { message: 'Refresh token expired' },
        { status: 401, statusText: 'Unauthorized' }
      );

      expect(result).toBeFalse();
      expect(service.getToken()).toBeNull();
      expect(service.isAuthenticated()).toBeFalse();
    });

    it('should return false and not throw on 5xx error', () => {
      let result: boolean | undefined;
      service.refresh().subscribe(r => result = r);
      httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(
        { message: 'Server error' },
        { status: 500, statusText: 'Internal Server Error' }
      );
      expect(result).toBeFalse();
      expect(service.isAuthenticated()).toBeFalse();
    });
  });

  // ── logout() ───────────────────────────────────────────────────────────────

  describe('logout()', () => {
    it('should clear token and user from memory immediately', (done) => {
      // Seed state via the login flow (avoids accessing private BehaviorSubjects)
      service.login({ email: 'test@example.com', password: 'secret' }).subscribe(() => {
        // Now token + user are set — call logout and assert BEFORE flushing the HTTP call
        service.logout();
        expect(service.getToken()).toBeNull();
        expect(service.getCurrentUser()).toBeNull();
        expect(service.isAuthenticated()).toBeFalse();
        // Now flush the logout HTTP call so httpMock.verify() is satisfied
        httpMock.expectOne(`${apiConfig.authUrl}/logout`).flush(null, { status: 204, statusText: 'No Content' });
        done();
      });
      // Flush the login call
      httpMock.expectOne(`${apiConfig.authUrl}/login`).flush(MOCK_API_RESPONSE);
    });

    it('should POST to /api/auth/logout with withCredentials', () => {
      service.logout();
      const req = httpMock.expectOne(`${apiConfig.authUrl}/logout`);
      expect(req.request.method).toBe('POST');
      expect(req.request.withCredentials).toBeTrue();
      req.flush(null, { status: 204, statusText: 'No Content' });
    });

    it('should not throw if the logout API call fails', () => {
      service.logout();
      // Flush an error response — service must swallow it
      httpMock.expectOne(`${apiConfig.authUrl}/logout`).flush(
        { message: 'Server error' },
        { status: 500, statusText: 'Internal Server Error' }
      );
      // If we reach here without an unhandled error, the test passes
      expect(service.isAuthenticated()).toBeFalse();
    });
  });

  // ── changePassword() ─────────────────────────────────────────────────────
  describe('changePassword()', () => {
    it('POSTs current+new password to /api/auth/change-password with credentials', () => {
      service.changePassword('Old1234!', 'New1234!').subscribe();

      const req = httpMock.expectOne(`${apiConfig.authUrl}/change-password`);
      expect(req.request.method).toBe('POST');
      expect(req.request.body).toEqual({
        currentPassword: 'Old1234!',
        newPassword: 'New1234!',
      });
      expect(req.request.withCredentials).toBeTrue();
      req.flush(null, { status: 204, statusText: 'No Content' });
    });
  });

  // ── initializeFromRefreshToken() ───────────────────────────────────────────

  describe('initializeFromRefreshToken()', () => {
    it('should resolve even when refresh returns 401 (does not block bootstrap)', async () => {
      const promise = service.initializeFromRefreshToken();
      httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(
        { message: 'No cookie' },
        { status: 401, statusText: 'Unauthorized' }
      );
      await expectAsync(promise).toBeResolved();
    });

    it('should resolve and authenticate user when refresh succeeds', async () => {
      const promise = service.initializeFromRefreshToken();
      httpMock.expectOne(`${apiConfig.authUrl}/refresh`).flush(MOCK_API_RESPONSE);
      await expectAsync(promise).toBeResolved();
      expect(service.isAuthenticated()).toBeTrue();
    });
  });

  // ── setCurrentUser() / getCurrentUser() ────────────────────────────────────

  describe('setCurrentUser()', () => {
    it('should update currentUser$ and mark authenticated', () => {
      service.setCurrentUser(MOCK_USER);
      expect(service.getCurrentUser()).toEqual(MOCK_USER);
      expect(service.isAuthenticated()).toBeTrue();
    });
  });

  // ── redirect URL helpers ────────────────────────────────────────────────────

  describe('redirect URL helpers', () => {
    it('should default redirect to /dashboard', () => {
      expect(service.getRedirectUrl()).toBe('/dashboard');
    });

    it('should store and return a custom redirect URL', () => {
      service.setRedirectUrl('/members');
      expect(service.getRedirectUrl()).toBe('/members');
    });
  });
});
