import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type { Page, BrowserContext } from 'playwright';
import type { LoggerPort as Logger } from 'apps/companion/main/automation/application/ports/LoggerPort';
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
import { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
import { PlaywrightAuthSessionService } from './PlaywrightAuthSessionService';
import { SessionCookieStore } from './SessionCookieStore';

describe('PlaywrightAuthSessionService.initiateLogin browser mode behaviour', () => {
  const originalEnv = { ...process.env };

  let mockBrowserSession: PlaywrightBrowserSession;
  let mockCookieStore: SessionCookieStore;
  let mockAuthFlow: IPlaywrightAuthFlow;
  let mockLogger: Logger;
  let mockPage: Page;

  beforeEach(() => {
    process.env = { ...originalEnv };

    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
      fatal: vi.fn(),
      child: vi.fn(),
      flush: vi.fn().mockResolvedValue(undefined),
    };

    mockPage = {
      goto: vi.fn().mockResolvedValue(undefined),
      url: vi.fn().mockReturnValue('https://members-ng.iracing.com/web/racing/hosted/browse-sessions'),
      isClosed: vi.fn().mockReturnValue(false),
    } as unknown as Page;

    mockBrowserSession = {
      connect: vi.fn().mockResolvedValue({ success: true }),
      disconnect: vi.fn().mockResolvedValue(undefined),
      getPersistentContext: vi.fn().mockReturnValue(null as unknown as BrowserContext | null),
      getContext: vi.fn().mockReturnValue(null as unknown as BrowserContext | null),
      getPage: vi.fn().mockReturnValue(mockPage),
      getUserDataDir: vi.fn().mockReturnValue(''),
    } as unknown as PlaywrightBrowserSession;

    mockCookieStore = {
      read: vi.fn().mockResolvedValue({
        cookies: [],
        origins: [],
      }),
      write: vi.fn().mockResolvedValue(undefined),
      delete: vi.fn().mockResolvedValue(undefined),
      validateCookies: vi.fn().mockReturnValue(AuthenticationState.UNKNOWN),
      getSessionExpiry: vi.fn(),
      getValidCookiesForUrl: vi.fn().mockReturnValue([]),
    } as unknown as SessionCookieStore;

    mockAuthFlow = {
      getLoginUrl: vi.fn().mockReturnValue('https://members-ng.iracing.com/login'),
      getPostLoginLandingUrl: vi.fn().mockReturnValue('https://members-ng.iracing.com/web/racing/hosted/browse-sessions'),
      isLoginUrl: vi.fn().mockReturnValue(false),
      isAuthenticatedUrl: vi.fn().mockReturnValue(true),
      isLoginSuccessUrl: vi.fn().mockReturnValue(true),
      detectAuthenticatedUi: vi.fn().mockResolvedValue(true),
      detectLoginUi: vi.fn().mockResolvedValue(false),
      navigateToAuthenticatedArea: vi.fn().mockResolvedValue(undefined),
      waitForPostLoginRedirect: vi.fn().mockResolvedValue(true),
    } as unknown as IPlaywrightAuthFlow;
  });

  afterEach(() => {
    process.env = { ...originalEnv };
    vi.restoreAllMocks();
  });

  function createService() {
    return new PlaywrightAuthSessionService(
      mockBrowserSession,
      mockCookieStore,
      mockAuthFlow,
      mockLogger,
      {
        navigationTimeoutMs: 1000,
        loginWaitTimeoutMs: 1000,
      },
    );
  }

  it('always forces headed browser for login regardless of browser mode configuration', async () => {
    const service = createService();
    const result = await service.initiateLogin();

    expect(result.isOk()).toBe(true);
    expect(mockBrowserSession.connect).toHaveBeenCalledWith(true);
  });

  it('navigates the headed page to the non-blank login URL', async () => {
    const service = createService();
    const result = await service.initiateLogin();

    expect(result.isOk()).toBe(true);
    expect(mockAuthFlow.getLoginUrl).toHaveBeenCalledTimes(1);

    expect(mockPage.goto).toHaveBeenCalledWith(
      'https://members-ng.iracing.com/login',
      expect.objectContaining({
        waitUntil: 'domcontentloaded',
      }),
    );

    const calledUrl = (mockPage.goto as unknown as ReturnType<typeof vi.fn>).mock.calls[0]![0] as string;
    expect(calledUrl).not.toEqual('about:blank');
  });

  it('propagates connection failure from browserSession.connect', async () => {
    (mockBrowserSession.connect as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      success: false,
      error: 'boom',
    });

    const service = createService();
    const result = await service.initiateLogin();

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err).toBeInstanceOf(Error);
    expect(err.message).toContain('boom');
  });

  it('logs explicit headed login message for human companion flow', async () => {
    const service = createService();
    const result = await service.initiateLogin();

    expect(result.isOk()).toBe(true);
    expect(mockLogger.info).toHaveBeenCalledWith(
      'Opening login in headed Playwright browser (forceHeaded=true)',
      expect.objectContaining({ forceHeaded: true }),
    );
  });
});