import { describe, expect, it, vi } from 'vitest';
import { Result } from '@core/shared/application/Result';
import { AuthService } from './AuthService';

class FakeAuthSessionPresenter {
  private model: any = null;
  reset() {
    this.model = null;
  }
  present(model: any) {
    this.model = model;
  }
  get responseModel() {
    if (!this.model) throw new Error('Presenter not presented');
    return this.model;
  }
}

class FakeCommandResultPresenter {
  private model: any = null;
  reset() {
    this.model = null;
  }
  present(model: any) {
    this.model = model;
  }
  get responseModel() {
    if (!this.model) throw new Error('Presenter not presented');
    return this.model;
  }
}

describe('AuthService', () => {
  it('getCurrentSession returns null when no session', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.getCurrentSession()).resolves.toBeNull();
  });

  it('getCurrentSession maps core session to DTO', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      {
        getCurrentSession: vi.fn(async () => ({
          token: 't1',
          user: { id: 'u1', email: null, displayName: 'D' },
        })),
        createSession: vi.fn(),
      } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.getCurrentSession()).resolves.toEqual({
      token: 't1',
      user: { userId: 'u1', email: '', displayName: 'D' },
    });
  });

  it('signupWithEmail creates session and returns AuthSessionDTO', async () => {
    const authSessionPresenter = new FakeAuthSessionPresenter();
    const identitySessionPort = {
      getCurrentSession: vi.fn(),
      createSession: vi.fn(async () => ({ token: 't2' })),
    };

    const signupUseCase = {
      execute: vi.fn(async () => {
        authSessionPresenter.present({ userId: 'u2', email: 'e2', displayName: 'd2' });
        return Result.ok(undefined);
      }),
    };

    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      identitySessionPort as any,
      { execute: vi.fn() } as any,
      signupUseCase as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      authSessionPresenter as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    const session = await service.signupWithEmail({
      email: 'e2',
      password: 'p2',
      displayName: 'd2',
    } as any);

    expect(signupUseCase.execute).toHaveBeenCalledWith({
      email: 'e2',
      password: 'p2',
      displayName: 'd2',
    });
    expect(identitySessionPort.createSession).toHaveBeenCalledWith({
      id: 'u2',
      displayName: 'd2',
      email: 'e2',
    });
    expect(session).toEqual({ token: 't2', user: { userId: 'u2', email: 'e2', displayName: 'd2' } });
  });

  it('signupWithEmail throws with fallback when no details.message', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(
      service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'd2' } as any),
    ).rejects.toThrow('Signup failed');
  });

  it('loginWithEmail creates session and returns AuthSessionDTO', async () => {
    const authSessionPresenter = new FakeAuthSessionPresenter();
    const identitySessionPort = {
      getCurrentSession: vi.fn(),
      createSession: vi.fn(async () => ({ token: 't3' })),
    };

    const loginUseCase = {
      execute: vi.fn(async () => {
        authSessionPresenter.present({ userId: 'u3', email: 'e3', displayName: 'd3' });
        return Result.ok(undefined);
      }),
    };

    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      identitySessionPort as any,
      loginUseCase as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      authSessionPresenter as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.loginWithEmail({ email: 'e3', password: 'p3' } as any)).resolves.toEqual({
      token: 't3',
      user: { userId: 'u3', email: 'e3', displayName: 'd3' },
    });

    expect(loginUseCase.execute).toHaveBeenCalledWith({ email: 'e3', password: 'p3' });
    expect(identitySessionPort.createSession).toHaveBeenCalledWith({
      id: 'u3',
      displayName: 'd3',
      email: 'e3',
    });
  });

  it('loginWithEmail throws on use case error and prefers details.message', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
      { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Bad login');
  });

  it('loginWithEmail throws with fallback when no details.message', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
      { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as any)) } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Login failed');
  });

  it('logout returns command result on success', async () => {
    const commandResultPresenter = new FakeCommandResultPresenter();
    const logoutUseCase = {
      execute: vi.fn(async () => {
        commandResultPresenter.present({ success: true });
        return Result.ok(undefined);
      }),
    };

    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      logoutUseCase as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      commandResultPresenter as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.logout()).resolves.toEqual({ success: true });
  });

  it('logout throws with fallback when no details.message', async () => {
    const service = new AuthService(
      { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      { execute: vi.fn() } as any,
      new FakeAuthSessionPresenter() as any,
      new FakeCommandResultPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
      new FakeAuthSessionPresenter() as any,
    );

    await expect(service.logout()).rejects.toThrow('Logout failed');
  });
});
