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

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

class FakeCommandResultPresenter {
  private model: unknown = null;
  reset() {
    this.model = null;
  }
  present(model: unknown) {
    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 never,
      { getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

    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 never,
      {
        getCurrentSession: vi.fn(async () => ({
          token: 't1',
          user: { id: 'u1', email: null, displayName: 'John' },
        })),
        createSession: vi.fn(),
      } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

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

  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 () => {
        return Result.ok({ userId: 'u2', email: 'e2', displayName: 'Jane Smith' });
      }),
    };

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

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

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

  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 never,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

    await expect(
      service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith' } as never),
    ).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 () => {
        return Result.ok({ userId: 'u3', email: 'e3', displayName: 'Bob Wilson' });
      }),
    };

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

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

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

  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 never,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
      { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

    await expect(service.loginWithEmail({ email: 'e', password: 'p' } as never)).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 never,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
      { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as never)) } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

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

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

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

    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 never,
      { getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
      { execute: vi.fn() } as never,
      { execute: vi.fn() } as never,
      new FakeAuthSessionPresenter() as never,
      new FakeCommandResultPresenter() as never,
      new FakeAuthSessionPresenter() as never,
      new FakeAuthSessionPresenter() as never,
    );

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