/**
 * Unit tests for the authentication strategy factory and mode-aware adapters.
 * Covers: none-mode dormant strategy, JwtSignerAdapter disabled behavior,
 * and LocalProviderAdapter / JwtSignerAdapter skipping readFileSync in none mode.
 */
import { ConfigService } from '@nestjs/config';
import { Test } from '@nestjs/testing';
import { generateKeyPairSync } from 'node:crypto';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { ACCOUNT_LOADER } from '../../domain/ports';
import { JwtSignerAdapter } from '../strategies/local-provider/jwt-signer.adapter';
import { LocalProviderAdapter } from '../strategies/local-provider/local-provider.adapter';

// ---------------------------------------------------------------------------
// JwtSignerAdapter — none mode
// ---------------------------------------------------------------------------

describe('JwtSignerAdapter — none mode', () => {
  let adapter: JwtSignerAdapter;

  beforeEach(async () => {
    const configService = {
      get: jest.fn((key: string, fallback?: unknown) => {
        if (key === 'OAUTH_STRATEGY') return 'none';
        return fallback;
      }),
    };

    const module = await Test.createTestingModule({
      providers: [JwtSignerAdapter, { provide: ConfigService, useValue: configService }],
    }).compile();

    adapter = module.get(JwtSignerAdapter);
  });

  it('constructs without reading any key file (no readFileSync error)', () => {
    // If this test reaches here without throwing, the constructor did not call readFileSync.
    expect(adapter).toBeDefined();
  });

  it('signAccess throws a clear "disabled" error in none mode', async () => {
    await expect(adapter.signAccess({ aci: 'test-id' })).rejects.toThrow(
      /token issuance disabled.*OAUTH_STRATEGY=none/i,
    );
  });

  it('signRefresh throws a clear "disabled" error in none mode', async () => {
    await expect(adapter.signRefresh()).rejects.toThrow(
      /token issuance disabled.*OAUTH_STRATEGY=none/i,
    );
  });
});

// ---------------------------------------------------------------------------
// JwtSignerAdapter — local-provider mode (keys present)
// ---------------------------------------------------------------------------

describe('JwtSignerAdapter — local-provider mode', () => {
  let tempDir: string;
  let privateKeyPath: string;

  beforeAll(() => {
    const { privateKey } = generateKeyPairSync('rsa', {
      modulusLength: 2048,
      privateKeyEncoding: { format: 'pem', type: 'pkcs1' },
      publicKeyEncoding: { format: 'pem', type: 'pkcs1' },
    });
    tempDir = mkdtempSync(join(tmpdir(), 'factory-spec-'));
    privateKeyPath = join(tempDir, 'oauth-private.key');
    writeFileSync(privateKeyPath, privateKey);
  });

  afterAll(() => {
    rmSync(tempDir, { recursive: true, force: true });
  });

  it('constructs and signs tokens when mode is local-provider and key file exists', async () => {
    const configService = {
      get: jest.fn((key: string, fallback?: unknown) => {
        if (key === 'OAUTH_STRATEGY') return 'local-provider';
        if (key === 'OAUTH_PRIVATE_KEY_PATH') return privateKeyPath;
        if (key === 'JWT_ACCESS_TTL_SECONDS') return 3600;
        if (key === 'JWT_REFRESH_TTL_SECONDS') return 2592000;
        return fallback;
      }),
    };

    const module = await Test.createTestingModule({
      providers: [JwtSignerAdapter, { provide: ConfigService, useValue: configService }],
    }).compile();

    const adapter = module.get(JwtSignerAdapter);
    const result = await adapter.signAccess({ aci: 'test-id' });
    expect(result.token).toBeDefined();
    expect(result.expiresAt).toBeInstanceOf(Date);
  });
});

// ---------------------------------------------------------------------------
// LocalProviderAdapter — none mode skips readFileSync
// ---------------------------------------------------------------------------

describe('LocalProviderAdapter — none mode skips readFileSync', () => {
  it('constructs without reading any public key file (no readFileSync error)', async () => {
    const configService = {
      get: jest.fn((key: string, fallback?: unknown) => {
        if (key === 'OAUTH_STRATEGY') return 'none';
        return fallback;
      }),
    };

    // In none mode there is no key file. The constructor must NOT call readFileSync.
    // If it does, it will throw because the default path doesn't exist.
    const module = await Test.createTestingModule({
      providers: [
        LocalProviderAdapter,
        { provide: ConfigService, useValue: configService },
        { provide: 'CACHE_MANAGER', useValue: {} },
        { provide: ACCOUNT_LOADER, useValue: {} },
      ],
    }).compile();

    const adapter = module.get(LocalProviderAdapter);
    expect(adapter).toBeDefined();
  });
});

// ---------------------------------------------------------------------------
// LocalProviderAdapter — aurora-hub mode skips readFileSync
// ---------------------------------------------------------------------------

describe('LocalProviderAdapter — aurora-hub mode skips readFileSync', () => {
  it('constructs without reading any public key file (no readFileSync error)', async () => {
    // aurora-hub mode: the local public key is irrelevant. Construction MUST NOT call
    // readFileSync regardless of whether OAUTH_PUBLIC_KEY_PATH is set.
    const configService = {
      get: jest.fn((key: string, fallback?: unknown) => {
        if (key === 'OAUTH_STRATEGY') return 'aurora-hub';
        // Point the key path at a guaranteed-missing file so the test stays discriminating
        // even in a dev env where ./.keys/ exists: reverting the dormant guard would make the
        // constructor readFileSync this path → ENOENT, turning the test red.
        if (key === 'OAUTH_PUBLIC_KEY_PATH') return '/nonexistent/aurora-hub-test.key';
        return fallback;
      }),
    };

    const module = await Test.createTestingModule({
      providers: [
        LocalProviderAdapter,
        { provide: ConfigService, useValue: configService },
        { provide: 'CACHE_MANAGER', useValue: {} },
        { provide: ACCOUNT_LOADER, useValue: {} },
      ],
    }).compile();

    const adapter = module.get(LocalProviderAdapter);
    expect(adapter).toBeDefined();
  });
});

// ---------------------------------------------------------------------------
// Authentication strategy factory — none mode returns a dormant strategy
//
// Note: the factory module imports AuroraHubConsumerAdapter which imports
// `jwks-rsa` (ESM-only — not transformable by ts-jest). To work around this
// known limitation (catalyst-backend-testing SKILL.md), we mock the entire
// adapter module so the import chain doesn't reach jwks-rsa, then call the
// factory's useFactory directly.
// ---------------------------------------------------------------------------

// Mock the ESM-importing module before any imports run.
jest.mock('../strategies/aurora-hub/aurora-hub-consumer.adapter', () => ({
  AuroraHubConsumerAdapter: class AuroraHubConsumerAdapterMock {},
}));

describe('authenticationStrategyProvider — none mode (direct factory call)', () => {
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  const { authenticationStrategyProvider } = require('./authentication-strategy.factory');

  it('returns a dormant strategy with valid getPassportOptions() when mode is none', () => {
    const configService = {
      get: jest.fn((key: string) => {
        if (key === 'OAUTH_STRATEGY') return 'none';
        return undefined;
      }),
    };

    // Minimal stub adapters — not used when mode=none
    const stubAuroraHub = {} as never;
    const stubLocalProvider = {} as never;

    const strategy = authenticationStrategyProvider.useFactory(
      configService,
      stubAuroraHub,
      stubLocalProvider,
    );

    const options = strategy.getPassportOptions();

    expect(options).toBeDefined();
    expect(options.jwtFromRequest).toBeDefined();
    expect(options.secretOrKey).toBeDefined();
  });

  it('validate() throws a clear error if invoked in none mode (should never happen)', () => {
    const configService = {
      get: jest.fn((key: string) => {
        if (key === 'OAUTH_STRATEGY') return 'none';
        return undefined;
      }),
    };

    const strategy = authenticationStrategyProvider.useFactory(configService, {}, {});

    // validate() throws synchronously (never returns a Promise) because it must
    // never be invoked — the guard short-circuits before passport in none mode.
    expect(() => strategy.validate()).toThrow('noneStrategy.validate() must never be invoked');
  });

  it('returns the aurora-hub adapter stub when mode is aurora-hub', () => {
    const configService = {
      get: jest.fn((key: string) => {
        if (key === 'OAUTH_STRATEGY') return 'aurora-hub';
        return undefined;
      }),
    };

    const stubAuroraHub = { getPassportOptions: jest.fn() } as never;
    const stubLocalProvider = {} as never;

    const strategy = authenticationStrategyProvider.useFactory(
      configService,
      stubAuroraHub,
      stubLocalProvider,
    );

    expect(strategy).toBe(stubAuroraHub);
  });

  it('returns the local-provider adapter stub when mode is local-provider', () => {
    const configService = {
      get: jest.fn((key: string) => {
        if (key === 'OAUTH_STRATEGY') return 'local-provider';
        return undefined;
      }),
    };

    const stubAuroraHub = {} as never;
    const stubLocalProvider = { getPassportOptions: jest.fn() } as never;

    const strategy = authenticationStrategyProvider.useFactory(
      configService,
      stubAuroraHub,
      stubLocalProvider,
    );

    expect(strategy).toBe(stubLocalProvider);
  });
});
