/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, afterEach, vi } from 'vitest';
import { ApiKeyAuthProvider } from './api-key-provider.js';
import * as resolver from './value-resolver.js';

vi.mock('./value-resolver.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./value-resolver.js')>();
  return {
    ...actual,
    resolveAuthValue: vi.fn(),
  };
});

describe('ApiKeyAuthProvider', () => {
  afterEach(() => {
    vi.unstubAllEnvs();
    vi.restoreAllMocks();
  });

  describe('initialization', () => {
    it('should initialize with literal API key', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'my-api-key',
      });
      await provider.initialize();

      const headers = await provider.headers();
      expect(headers).toEqual({ 'X-API-Key': 'my-api-key' });
    });

    it('should resolve API key from environment variable', async () => {
      vi.stubEnv('TEST_API_KEY', 'env-api-key');
      vi.mocked(resolver.resolveAuthValue).mockResolvedValue('env-api-key');

      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: '$TEST_API_KEY',
      });
      await provider.initialize();

      const headers = await provider.headers();
      expect(headers).toEqual({ 'X-API-Key': 'env-api-key' });
    });

    it('should throw if environment variable is not set', async () => {
      vi.mocked(resolver.resolveAuthValue).mockRejectedValue(
        new Error("Environment variable 'MISSING_KEY_12345' is not set"),
      );

      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: '$MISSING_KEY_12345',
      });

      await expect(provider.initialize()).rejects.toThrow(
        "Environment variable 'MISSING_KEY_12345' is not set",
      );
    });
  });

  describe('headers', () => {
    it('should throw if not initialized', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'test-key',
      });

      await expect(provider.headers()).rejects.toThrow('not initialized');
    });

    it('should use custom header name', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'my-key',
        name: 'X-Custom-Auth',
      });
      await provider.initialize();

      const headers = await provider.headers();
      expect(headers).toEqual({ 'X-Custom-Auth': 'my-key' });
    });

    it('should use default header name X-API-Key', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'my-key',
      });
      await provider.initialize();

      const headers = await provider.headers();
      expect(headers).toEqual({ 'X-API-Key': 'my-key' });
    });
  });

  describe('shouldRetryWithHeaders', () => {
    it('should return undefined for non-auth errors', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'test-key',
      });
      await provider.initialize();

      const result = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 500 }),
      );
      expect(result).toBeUndefined();
    });

    it('should return undefined for literal keys on 401 (same headers would fail again)', async () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'test-key',
      });
      await provider.initialize();

      const result = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 401 }),
      );
      expect(result).toBeUndefined();
    });

    it('should return undefined for env-var keys on 403', async () => {
      vi.stubEnv('RETRY_TEST_KEY', 'some-key');
      vi.mocked(resolver.resolveAuthValue).mockResolvedValue('some-key');

      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: '$RETRY_TEST_KEY',
      });
      await provider.initialize();

      const result = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 403 }),
      );
      expect(result).toBeUndefined();
    });

    it('should re-resolve and return headers for command keys on 401', async () => {
      vi.mocked(resolver.resolveAuthValue)
        .mockResolvedValueOnce('initial-key')
        .mockResolvedValueOnce('refreshed-key');

      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: '!some command',
      });
      await provider.initialize();

      const result = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 401 }),
      );
      expect(result).toEqual({ 'X-API-Key': 'refreshed-key' });
    });

    it('should stop retrying after MAX_AUTH_RETRIES', async () => {
      vi.mocked(resolver.resolveAuthValue).mockResolvedValue('rotating-key');

      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: '!some command',
      });
      await provider.initialize();

      const r1 = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 401 }),
      );
      expect(r1).toBeDefined();

      const r2 = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 401 }),
      );
      expect(r2).toBeDefined();

      const r3 = await provider.shouldRetryWithHeaders(
        {},
        new Response(null, { status: 401 }),
      );
      expect(r3).toBeUndefined();
    });
  });

  describe('type property', () => {
    it('should have type apiKey', () => {
      const provider = new ApiKeyAuthProvider({
        type: 'apiKey',
        key: 'test',
      });
      expect(provider.type).toBe('apiKey');
    });
  });
});
