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

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { EventEmitter } from 'events';
import { type ChildProcess } from 'child_process';
import type { Config } from '../config/config.js';
import {
  generateCodeChallenge,
  generateCodeVerifier,
  generatePKCEPair,
  isDeviceAuthorizationSuccess,
  isDeviceTokenPending,
  isDeviceTokenSuccess,
  isErrorResponse,
  QwenOAuth2Client,
  type DeviceAuthorizationResponse,
  type DeviceTokenResponse,
  type ErrorData,
  type QwenCredentials,
} from './qwenOAuth2.js';
import {
  SharedTokenManager,
  TokenManagerError,
  TokenError,
} from './sharedTokenManager.js';

interface MockSharedTokenManager {
  getValidCredentials(qwenClient: QwenOAuth2Client): Promise<QwenCredentials>;
  getCurrentCredentials(): QwenCredentials | null;
  clearCache(): void;
}

// Mock SharedTokenManager
vi.mock('./sharedTokenManager.js', () => ({
  SharedTokenManager: class {
    private static instance: MockSharedTokenManager | null = null;

    static getInstance() {
      if (!this.instance) {
        this.instance = new this();
      }
      return this.instance;
    }

    async getValidCredentials(
      qwenClient: QwenOAuth2Client,
    ): Promise<QwenCredentials> {
      // Try to get credentials from the client first
      const clientCredentials = qwenClient.getCredentials();
      if (clientCredentials && clientCredentials.access_token) {
        return clientCredentials;
      }

      // Fall back to default mock credentials if client has none
      return {
        access_token: 'new-access-token',
        refresh_token: 'valid-refresh-token',
        resource_url: undefined,
        token_type: 'Bearer',
        expiry_date: Date.now() + 3600000,
      };
    }

    getCurrentCredentials(): QwenCredentials | null {
      // Return null to let the client manage its own credentials
      return null;
    }

    clearCache(): void {
      // Do nothing in mock
    }
  },
  TokenManagerError: class extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'TokenManagerError';
    }
  },
  TokenError: {
    REFRESH_FAILED: 'REFRESH_FAILED',
    NO_REFRESH_TOKEN: 'NO_REFRESH_TOKEN',
    LOCK_TIMEOUT: 'LOCK_TIMEOUT',
    FILE_ACCESS_ERROR: 'FILE_ACCESS_ERROR',
    NETWORK_ERROR: 'NETWORK_ERROR',
  },
}));

// Mock qrcode-terminal
vi.mock('qrcode-terminal', () => ({
  default: {
    generate: vi.fn(),
  },
}));

// Mock open
vi.mock('open', () => ({
  default: vi.fn(),
}));

// Mock process.stdout.write
vi.mock('process', () => ({
  stdout: {
    write: vi.fn(),
  },
}));

// Mock file system operations
vi.mock('node:fs', () => ({
  promises: {
    readFile: vi.fn(),
    writeFile: vi.fn(),
    unlink: vi.fn(),
    mkdir: vi.fn().mockResolvedValue(undefined),
  },
}));

describe('PKCE Code Generation', () => {
  describe('generateCodeVerifier', () => {
    it('should generate a code verifier with correct length', () => {
      const codeVerifier = generateCodeVerifier();
      expect(codeVerifier).toMatch(/^[A-Za-z0-9_-]{43}$/);
    });

    it('should generate different verifiers on subsequent calls', () => {
      const verifier1 = generateCodeVerifier();
      const verifier2 = generateCodeVerifier();
      expect(verifier1).not.toBe(verifier2);
    });
  });

  describe('generateCodeChallenge', () => {
    it('should generate code challenge from verifier', () => {
      const verifier = 'test-verifier-1234567890abcdefghijklmnopqrst';
      const challenge = generateCodeChallenge(verifier);

      // Should be base64url encoded
      expect(challenge).toMatch(/^[A-Za-z0-9_-]+$/);
      expect(challenge).not.toBe(verifier);
    });
  });

  describe('generatePKCEPair', () => {
    it('should generate valid PKCE pair', () => {
      const { code_verifier, code_challenge } = generatePKCEPair();

      expect(code_verifier).toMatch(/^[A-Za-z0-9_-]{43}$/);
      expect(code_challenge).toMatch(/^[A-Za-z0-9_-]+$/);
      expect(code_verifier).not.toBe(code_challenge);
    });
  });
});

describe('Type Guards', () => {
  describe('isDeviceAuthorizationSuccess', () => {
    it('should return true for successful authorization response', () => {
      const expectedBaseUrl = process.env['DEBUG']
        ? 'https://pre4-chat.qwen.ai'
        : 'https://chat.qwen.ai';

      const successResponse: DeviceAuthorizationResponse = {
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: `${expectedBaseUrl}/device`,
        verification_uri_complete: `${expectedBaseUrl}/device?code=TEST123`,
        expires_in: 1800,
      };

      expect(isDeviceAuthorizationSuccess(successResponse)).toBe(true);
    });

    it('should return false for error response', () => {
      const errorResponse: DeviceAuthorizationResponse = {
        error: 'INVALID_REQUEST',
        error_description: 'The request parameters are invalid',
      };

      expect(isDeviceAuthorizationSuccess(errorResponse)).toBe(false);
    });
  });

  describe('isDeviceTokenPending', () => {
    it('should return true for pending response', () => {
      const pendingResponse: DeviceTokenResponse = {
        status: 'pending',
      };

      expect(isDeviceTokenPending(pendingResponse)).toBe(true);
    });

    it('should return false for success response', () => {
      const successResponse: DeviceTokenResponse = {
        access_token: 'valid-access-token',
        refresh_token: 'valid-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      };

      expect(isDeviceTokenPending(successResponse)).toBe(false);
    });

    it('should return false for error response', () => {
      const errorResponse: DeviceTokenResponse = {
        error: 'ACCESS_DENIED',
        error_description: 'User denied the authorization request',
      };

      expect(isDeviceTokenPending(errorResponse)).toBe(false);
    });
  });

  describe('isDeviceTokenSuccess', () => {
    it('should return true for successful token response', () => {
      const successResponse: DeviceTokenResponse = {
        access_token: 'valid-access-token',
        refresh_token: 'valid-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      };

      expect(isDeviceTokenSuccess(successResponse)).toBe(true);
    });

    it('should return false for pending response', () => {
      const pendingResponse: DeviceTokenResponse = {
        status: 'pending',
      };

      expect(isDeviceTokenSuccess(pendingResponse)).toBe(false);
    });

    it('should return false for error response', () => {
      const errorResponse: DeviceTokenResponse = {
        error: 'ACCESS_DENIED',
        error_description: 'User denied the authorization request',
      };

      expect(isDeviceTokenSuccess(errorResponse)).toBe(false);
    });

    it('should return false for null access token', () => {
      const nullTokenResponse: DeviceTokenResponse = {
        access_token: null,
        token_type: 'Bearer',
        expires_in: 3600,
      };

      expect(isDeviceTokenSuccess(nullTokenResponse)).toBe(false);
    });

    it('should return false for empty access token', () => {
      const emptyTokenResponse: DeviceTokenResponse = {
        access_token: '',
        token_type: 'Bearer',
        expires_in: 3600,
      };

      expect(isDeviceTokenSuccess(emptyTokenResponse)).toBe(false);
    });
  });

  describe('isErrorResponse', () => {
    it('should return true for error responses', () => {
      const errorResponse: ErrorData = {
        error: 'INVALID_REQUEST',
        error_description: 'The request parameters are invalid',
      };

      expect(isErrorResponse(errorResponse)).toBe(true);
    });

    it('should return false for successful responses', () => {
      const successResponse: DeviceAuthorizationResponse = {
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      };

      expect(isErrorResponse(successResponse)).toBe(false);
    });
  });
});

describe('QwenOAuth2Client', () => {
  let client: QwenOAuth2Client;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    // Create client instance
    client = new QwenOAuth2Client();

    // Mock fetch
    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  describe('requestDeviceAuthorization', () => {
    it('should successfully request device authorization', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.requestDeviceAuthorization({
        scope: 'openid profile email model.completion',
        code_challenge: 'test-challenge',
        code_challenge_method: 'S256',
      });

      expect(result).toEqual({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      });
    });

    it('should handle error response', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          error: 'INVALID_REQUEST',
          error_description: 'The request parameters are invalid',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(
        client.requestDeviceAuthorization({
          scope: 'openid profile email model.completion',
          code_challenge: 'test-challenge',
          code_challenge_method: 'S256',
        }),
      ).rejects.toThrow(
        'Device authorization failed: INVALID_REQUEST - The request parameters are invalid',
      );
    });
  });

  describe('refreshAccessToken', () => {
    beforeEach(() => {
      // Set up client with credentials
      client.setCredentials({
        access_token: 'old-token',
        refresh_token: 'test-refresh-token',
        token_type: 'Bearer',
      });
    });

    it('should successfully refresh access token', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          token_type: 'Bearer',
          expires_in: 3600,
          resource_url: 'https://new-endpoint.com',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.refreshAccessToken();

      expect(result).toEqual({
        access_token: 'new-access-token',
        token_type: 'Bearer',
        expires_in: 3600,
        resource_url: 'https://new-endpoint.com',
      });

      // Verify credentials were updated
      const credentials = client.getCredentials();
      expect(credentials.access_token).toBe('new-access-token');
    });

    it('should handle refresh error', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          error: 'INVALID_GRANT',
          error_description: 'The refresh token is invalid',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(client.refreshAccessToken()).rejects.toThrow(
        'Token refresh failed: INVALID_GRANT - The refresh token is invalid',
      );
    });

    it('should successfully refresh access token and update credentials', async () => {
      // Clear any previous calls
      vi.clearAllMocks();

      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          token_type: 'Bearer',
          expires_in: 3600,
          resource_url: 'https://new-endpoint.com',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.refreshAccessToken();

      // Verify the response
      expect(result).toMatchObject({
        access_token: 'new-access-token',
        token_type: 'Bearer',
        expires_in: 3600,
        resource_url: 'https://new-endpoint.com',
      });

      // Verify credentials were updated
      const credentials = client.getCredentials();
      expect(credentials).toMatchObject({
        access_token: 'new-access-token',
        token_type: 'Bearer',
        refresh_token: 'test-refresh-token', // Should preserve existing refresh token
        resource_url: 'https://new-endpoint.com',
      });
      expect(credentials.expiry_date).toBeDefined();
    });

    it('should use new refresh token if provided in response', async () => {
      // Clear any previous calls
      vi.clearAllMocks();

      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          token_type: 'Bearer',
          expires_in: 3600,
          refresh_token: 'new-refresh-token', // New refresh token provided
          resource_url: 'https://new-endpoint.com',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.refreshAccessToken();

      // Verify the credentials contain the new refresh token
      const credentials = client.getCredentials();
      expect(credentials.refresh_token).toBe('new-refresh-token');
    });
  });

  describe('getAccessToken', () => {
    it('should return access token if valid and not expired', async () => {
      // Set valid credentials
      client.setCredentials({
        access_token: 'valid-token',
        expiry_date: Date.now() + 60 * 60 * 1000, // 1 hour from now
      });

      const result = await client.getAccessToken();
      expect(result.token).toBe('valid-token');
    });

    it('should refresh token if access token is expired', async () => {
      // Set expired credentials with refresh token
      client.setCredentials({
        access_token: 'expired-token',
        refresh_token: 'valid-refresh-token',
        expiry_date: Date.now() - 1000, // 1 second ago
      });

      // Override the client's SharedTokenManager instance directly
      (
        client as unknown as {
          sharedManager: {
            getValidCredentials: () => Promise<QwenCredentials>;
          };
        }
      ).sharedManager = {
        getValidCredentials: vi.fn().mockResolvedValue({
          access_token: 'new-access-token',
          refresh_token: 'valid-refresh-token',
          token_type: 'Bearer',
          expiry_date: Date.now() + 3600000,
        }),
      };

      const result = await client.getAccessToken();
      expect(result.token).toBe('new-access-token');
    });

    it('should return undefined if no access token and no refresh token', async () => {
      client.setCredentials({});

      // Override the client's SharedTokenManager instance directly
      (
        client as unknown as {
          sharedManager: {
            getValidCredentials: () => Promise<QwenCredentials>;
          };
        }
      ).sharedManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('No credentials available')),
      };

      const result = await client.getAccessToken();
      expect(result.token).toBeUndefined();
    });
  });

  describe('pollDeviceToken', () => {
    it('should successfully poll for device token', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          refresh_token: 'new-refresh-token',
          token_type: 'Bearer',
          expires_in: 3600,
          scope: 'openid profile email model.completion',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.pollDeviceToken({
        device_code: 'test-device-code',
        code_verifier: 'test-code-verifier',
      });

      expect(result).toEqual({
        access_token: 'new-access-token',
        refresh_token: 'new-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      });
    });

    it('should return pending status when authorization is pending', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          status: 'pending',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.pollDeviceToken({
        device_code: 'test-device-code',
        code_verifier: 'test-code-verifier',
      });

      expect(result).toEqual({
        status: 'pending',
      });
    });

    it('should handle HTTP error responses', async () => {
      const mockResponse = {
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: async () => 'Invalid device code',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(
        client.pollDeviceToken({
          device_code: 'invalid-device-code',
          code_verifier: 'test-code-verifier',
        }),
      ).rejects.toThrow('Device token poll failed: 400 Bad Request');
    });

    it('should include status code in error for better handling', async () => {
      const mockResponse = {
        ok: false,
        status: 429,
        statusText: 'Too Many Requests',
        text: async () => 'Rate limited',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      try {
        await client.pollDeviceToken({
          device_code: 'test-device-code',
          code_verifier: 'test-code-verifier',
        });
      } catch (error) {
        expect((error as Error & { status?: number }).status).toBe(429);
      }
    });

    it('should handle authorization_pending with HTTP 400 according to RFC 8628', async () => {
      const errorData = {
        error: 'authorization_pending',
        error_description: 'The authorization request is still pending',
      };
      const mockResponse = {
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: async () => JSON.stringify(errorData),
        json: async () => errorData,
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.pollDeviceToken({
        device_code: 'test-device-code',
        code_verifier: 'test-code-verifier',
      });

      expect(result).toEqual({
        status: 'pending',
      });
    });

    it('should handle slow_down with HTTP 429 according to RFC 8628', async () => {
      const errorData = {
        error: 'slow_down',
        error_description: 'The client is polling too frequently',
      };
      const mockResponse = {
        ok: false,
        status: 429,
        statusText: 'Too Many Requests',
        text: async () => JSON.stringify(errorData),
        json: async () => errorData,
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      const result = await client.pollDeviceToken({
        device_code: 'test-device-code',
        code_verifier: 'test-code-verifier',
      });

      expect(result).toEqual({
        status: 'pending',
        slowDown: true,
      });
    });
  });

  describe('refreshAccessToken error handling', () => {
    beforeEach(() => {
      client.setCredentials({
        access_token: 'old-token',
        refresh_token: 'test-refresh-token',
        token_type: 'Bearer',
      });
    });

    it('should throw error if no refresh token available', async () => {
      client.setCredentials({ access_token: 'token' });

      await expect(client.refreshAccessToken()).rejects.toThrow(
        'No refresh token available',
      );
    });

    it('should handle 400 status as expired refresh token', async () => {
      const mockResponse = {
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: async () => 'Refresh token expired',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(client.refreshAccessToken()).rejects.toThrow(
        "Refresh token expired or invalid. Please use '/auth' to re-authenticate.",
      );
    });

    it('should handle other HTTP error statuses', async () => {
      const mockResponse = {
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        text: async () => 'Server error',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(client.refreshAccessToken()).rejects.toThrow(
        'Token refresh failed: 500 Internal Server Error',
      );
    });
  });

  describe('credentials management', () => {
    it('should set and get credentials correctly', () => {
      const credentials = {
        access_token: 'test-token',
        refresh_token: 'test-refresh',
        token_type: 'Bearer',
        expiry_date: Date.now() + 3600000,
      };

      client.setCredentials(credentials);
      expect(client.getCredentials()).toEqual(credentials);
    });

    it('should handle empty credentials', () => {
      client.setCredentials({});
      expect(client.getCredentials()).toEqual({});
    });
  });
});

describe('getQwenOAuthClient', () => {
  let mockConfig: Config;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    mockConfig = {
      isBrowserLaunchSuppressed: vi.fn().mockReturnValue(false),
    } as unknown as Config;

    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  it('should load cached credentials if available', async () => {
    const fs = await import('node:fs');
    const mockCredentials = {
      access_token: 'cached-token',
      refresh_token: 'cached-refresh',
      token_type: 'Bearer',
      expiry_date: Date.now() + 3600000,
    };

    vi.mocked(fs.promises.readFile).mockResolvedValue(
      JSON.stringify(mockCredentials),
    );

    // Mock SharedTokenManager to use cached credentials
    const mockTokenManager = {
      getValidCredentials: vi.fn().mockResolvedValue(mockCredentials),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    const client = await import('./qwenOAuth2.js').then((module) =>
      module.getQwenOAuthClient(mockConfig),
    );

    expect(client).toBeInstanceOf(Object);
    expect(mockTokenManager.getValidCredentials).toHaveBeenCalled();

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle cached credentials refresh failure', async () => {
    const fs = await import('node:fs');
    const mockCredentials = {
      access_token: 'cached-token',
      refresh_token: 'expired-refresh',
      token_type: 'Bearer',
      expiry_date: Date.now() + 3600000, // Valid expiry time so loadCachedQwenCredentials returns true
    };

    vi.mocked(fs.promises.readFile).mockResolvedValue(
      JSON.stringify(mockCredentials),
    );

    // Mock SharedTokenManager to fail with a specific error
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('Token refresh failed')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock device flow to also fail
    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        error: 'invalid_request',
        error_description: 'Invalid request parameters',
      }),
    };
    vi.mocked(global.fetch).mockResolvedValue(mockAuthResponse as Response);

    // The function should handle the invalid cached credentials and throw the expected error
    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow('Device authorization flow failed');

    SharedTokenManager.getInstance = originalGetInstance;
  });
});

describe('CredentialsClearRequiredError', () => {
  it('should create error with correct name and message', async () => {
    const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');

    const message = 'Test error message';
    const originalError = { status: 400, response: 'Bad Request' };
    const error = new CredentialsClearRequiredError(message, originalError);

    expect(error.name).toBe('CredentialsClearRequiredError');
    expect(error.message).toBe(message);
    expect(error.originalError).toBe(originalError);
    expect(error instanceof Error).toBe(true);
  });

  it('should work without originalError', async () => {
    const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');

    const message = 'Test error message';
    const error = new CredentialsClearRequiredError(message);

    expect(error.name).toBe('CredentialsClearRequiredError');
    expect(error.message).toBe(message);
    expect(error.originalError).toBeUndefined();
  });
});

describe('clearQwenCredentials', () => {
  it('should successfully clear credentials file', async () => {
    const { promises: fs } = await import('node:fs');
    const { clearQwenCredentials } = await import('./qwenOAuth2.js');

    vi.mocked(fs.unlink).mockResolvedValue(undefined);

    await expect(clearQwenCredentials()).resolves.not.toThrow();
    expect(fs.unlink).toHaveBeenCalled();
  });

  it('should handle file not found error gracefully', async () => {
    const { promises: fs } = await import('node:fs');
    const { clearQwenCredentials } = await import('./qwenOAuth2.js');

    const notFoundError = new Error('File not found');
    (notFoundError as Error & { code: string }).code = 'ENOENT';
    vi.mocked(fs.unlink).mockRejectedValue(notFoundError);

    await expect(clearQwenCredentials()).resolves.not.toThrow();
  });

  it('should handle other file system errors gracefully', async () => {
    const { promises: fs } = await import('node:fs');
    const { clearQwenCredentials } = await import('./qwenOAuth2.js');

    const permissionError = new Error('Permission denied');
    vi.mocked(fs.unlink).mockRejectedValue(permissionError);

    // Should not throw but may log warning
    await expect(clearQwenCredentials()).resolves.not.toThrow();
  });
});

describe('QwenOAuth2Client - Additional Error Scenarios', () => {
  let client: QwenOAuth2Client;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    client = new QwenOAuth2Client();
    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  describe('requestDeviceAuthorization HTTP errors', () => {
    it('should handle HTTP error response with non-ok status', async () => {
      const mockResponse = {
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        text: async () => 'Server error occurred',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(
        client.requestDeviceAuthorization({
          scope: 'openid profile email model.completion',
          code_challenge: 'test-challenge',
          code_challenge_method: 'S256',
        }),
      ).rejects.toThrow(
        'Device authorization failed: 500 Internal Server Error. Response: Server error occurred',
      );
    });
  });
});

describe('getQwenOAuthClient - Enhanced Error Scenarios', () => {
  let mockConfig: Config;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    mockConfig = {
      isBrowserLaunchSuppressed: vi.fn().mockReturnValue(false),
    } as unknown as Config;

    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  it('should handle generic refresh token errors', async () => {
    const { promises: fs } = await import('node:fs');
    const mockCredentials = {
      access_token: 'cached-token',
      refresh_token: 'some-refresh-token',
      token_type: 'Bearer',
      expiry_date: Date.now() + 3600000,
    };

    vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockCredentials));

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('Refresh failed')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock device flow to also fail
    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        error: 'invalid_request',
        error_description: 'Invalid request parameters',
      }),
    };
    vi.mocked(global.fetch).mockResolvedValue(mockAuthResponse as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow('Device authorization flow failed');

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle different authentication failure reasons - timeout', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock device authorization to succeed but polling to timeout
    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 0.1, // Very short timeout for testing
      }),
    };

    const mockPendingResponse = {
      ok: true,
      json: async () => ({
        status: 'pending',
      }),
    };

    global.fetch = vi
      .fn()
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockPendingResponse as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow('Authorization timeout, please restart the process.');

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle authentication failure reason - rate limit', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock device authorization to succeed but polling to get rate limited
    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mockRateLimitResponse = {
      ok: false,
      status: 429,
      statusText: 'Too Many Requests',
      text: async () => 'Rate limited',
    };

    global.fetch = vi
      .fn()
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockRateLimitResponse as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow(
      'Too many requests. The server is rate limiting our requests. Please select a different authentication method or try again later.',
    );

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle authentication failure reason - error', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock device authorization to fail
    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        error: 'invalid_request',
        error_description: 'Invalid request parameters',
      }),
    };

    global.fetch = vi.fn().mockResolvedValue(mockAuthResponse as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow('Device authorization flow failed');

    SharedTokenManager.getInstance = originalGetInstance;
  });
});

describe('authWithQwenDeviceFlow - Comprehensive Testing', () => {
  let mockConfig: Config;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    mockConfig = {
      isBrowserLaunchSuppressed: vi.fn().mockReturnValue(false),
    } as unknown as Config;

    originalFetch = global.fetch;
    global.fetch = vi.fn();

    // Mock setTimeout to avoid real delays in tests
    vi.useFakeTimers();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
    vi.useRealTimers();
  });

  it('should handle device authorization error response', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        error: 'invalid_client',
        error_description: 'Client authentication failed',
      }),
    };

    global.fetch = vi.fn().mockResolvedValue(mockAuthResponse as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow('Device authorization flow failed');

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle successful authentication flow', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mockTokenResponse = {
      ok: true,
      json: async () => ({
        access_token: 'new-access-token',
        refresh_token: 'new-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      }),
    };

    vi.mocked(global.fetch)
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockTokenResponse as Response);

    const client = await import('./qwenOAuth2.js').then((module) =>
      module.getQwenOAuthClient(mockConfig),
    );

    expect(client).toBeInstanceOf(Object);
  });

  it('should handle 401 error during token polling', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mock401Response = {
      ok: false,
      status: 401,
      statusText: 'Unauthorized',
      text: async () => 'Device code expired',
    };

    global.fetch = vi
      .fn()
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mock401Response as Response);

    await expect(
      import('./qwenOAuth2.js').then((module) =>
        module.getQwenOAuthClient(mockConfig),
      ),
    ).rejects.toThrow(
      'Device code expired or invalid, please restart the authorization process.',
    );

    SharedTokenManager.getInstance = originalGetInstance;
  });

  it('should handle token polling with browser launch suppressed', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock SharedTokenManager to fail initially so device flow is used
    const mockTokenManager = {
      getValidCredentials: vi
        .fn()
        .mockRejectedValue(new Error('No credentials')),
    };

    const originalGetInstance = SharedTokenManager.getInstance;
    SharedTokenManager.getInstance = vi.fn().mockReturnValue(mockTokenManager);

    // Mock browser launch as suppressed
    mockConfig.isBrowserLaunchSuppressed = vi.fn().mockReturnValue(true);

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mockTokenResponse = {
      ok: true,
      json: async () => ({
        access_token: 'new-access-token',
        refresh_token: 'new-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      }),
    };

    global.fetch = vi
      .fn()
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockTokenResponse as Response);

    const client = await import('./qwenOAuth2.js').then((module) =>
      module.getQwenOAuthClient(mockConfig),
    );

    expect(client).toBeInstanceOf(Object);
    expect(mockConfig.isBrowserLaunchSuppressed).toHaveBeenCalled();

    SharedTokenManager.getInstance = originalGetInstance;
  });
});

describe('Browser Launch and Error Handling', () => {
  let mockConfig: Config;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    mockConfig = {
      isBrowserLaunchSuppressed: vi.fn().mockReturnValue(false),
    } as unknown as Config;

    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  it('should handle browser launch failure gracefully', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock open to throw error
    const open = await import('open');
    vi.mocked(open.default).mockRejectedValue(
      new Error('Browser launch failed'),
    );

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mockTokenResponse = {
      ok: true,
      json: async () => ({
        access_token: 'new-access-token',
        refresh_token: 'new-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      }),
    };

    vi.mocked(global.fetch)
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockTokenResponse as Response);

    const client = await import('./qwenOAuth2.js').then((module) =>
      module.getQwenOAuthClient(mockConfig),
    );

    expect(client).toBeInstanceOf(Object);
  });

  it('should handle browser child process error gracefully', async () => {
    const { promises: fs } = await import('node:fs');
    vi.mocked(fs.readFile).mockRejectedValue(
      new Error('No cached credentials'),
    );

    // Mock open to return a child process that will emit error
    const open = await import('open');
    const mockChildProcess = {
      on: vi.fn((event: string, callback: (error: Error) => void) => {
        if (event === 'error') {
          // Call the error handler immediately for testing
          setTimeout(() => callback(new Error('Process spawn failed')), 0);
        }
      }),
    };
    vi.mocked(open.default).mockResolvedValue(
      mockChildProcess as unknown as ChildProcess,
    );

    const mockAuthResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    const mockTokenResponse = {
      ok: true,
      json: async () => ({
        access_token: 'new-access-token',
        refresh_token: 'new-refresh-token',
        token_type: 'Bearer',
        expires_in: 3600,
        scope: 'openid profile email model.completion',
      }),
    };

    vi.mocked(global.fetch)
      .mockResolvedValueOnce(mockAuthResponse as Response)
      .mockResolvedValue(mockTokenResponse as Response);

    const client = await import('./qwenOAuth2.js').then((module) =>
      module.getQwenOAuthClient(mockConfig),
    );

    expect(client).toBeInstanceOf(Object);
  });
});

describe('Event Emitter Integration', () => {
  it('should export qwenOAuth2Events as EventEmitter', async () => {
    const { qwenOAuth2Events } = await import('./qwenOAuth2.js');
    expect(qwenOAuth2Events).toBeInstanceOf(EventEmitter);
  });

  it('should define correct event enum values', async () => {
    const { QwenOAuth2Event } = await import('./qwenOAuth2.js');
    expect(QwenOAuth2Event.AuthUri).toBe('auth-uri');
    expect(QwenOAuth2Event.AuthProgress).toBe('auth-progress');
    expect(QwenOAuth2Event.AuthCancel).toBe('auth-cancel');
  });
});

describe('Utility Functions', () => {
  describe('objectToUrlEncoded', () => {
    it('should encode object properties to URL-encoded format', async () => {
      // Since objectToUrlEncoded is private, we test it indirectly through the client
      const objectToUrlEncoded = (data: Record<string, string>): string =>
        Object.keys(data)
          .map(
            (key) =>
              `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`,
          )
          .join('&');

      const testData = {
        client_id: 'test-client',
        scope: 'openid profile',
        redirect_uri: 'https://example.com/callback',
      };

      const result = objectToUrlEncoded(testData);

      expect(result).toContain('client_id=test-client');
      expect(result).toContain('scope=openid%20profile');
      expect(result).toContain(
        'redirect_uri=https%3A%2F%2Fexample.com%2Fcallback',
      );
    });

    it('should handle special characters', async () => {
      const objectToUrlEncoded = (data: Record<string, string>): string =>
        Object.keys(data)
          .map(
            (key) =>
              `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`,
          )
          .join('&');

      const testData = {
        'param with spaces': 'value with spaces',
        'param&with&amps': 'value&with&amps',
        'param=with=equals': 'value=with=equals',
      };

      const result = objectToUrlEncoded(testData);

      expect(result).toContain('param%20with%20spaces=value%20with%20spaces');
      expect(result).toContain('param%26with%26amps=value%26with%26amps');
      expect(result).toContain('param%3Dwith%3Dequals=value%3Dwith%3Dequals');
    });

    it('should handle empty object', async () => {
      const objectToUrlEncoded = (data: Record<string, string>): string =>
        Object.keys(data)
          .map(
            (key) =>
              `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`,
          )
          .join('&');

      const result = objectToUrlEncoded({});
      expect(result).toBe('');
    });
  });

  describe('getQwenCachedCredentialPath', () => {
    it('should return correct path to cached credentials', async () => {
      const os = await import('os');
      const path = await import('path');

      const expectedPath = path.join(os.homedir(), '.qwen', 'oauth_creds.json');

      // Since this is a private function, we test it indirectly through clearQwenCredentials
      const { promises: fs } = await import('node:fs');
      const { clearQwenCredentials } = await import('./qwenOAuth2.js');

      vi.mocked(fs.unlink).mockResolvedValue(undefined);

      await clearQwenCredentials();

      expect(fs.unlink).toHaveBeenCalledWith(expectedPath);
    });
  });
});

describe('Credential Caching Functions', () => {
  describe('cacheQwenCredentials', () => {
    it('should create directory and write credentials to file', async () => {
      // Mock the internal cacheQwenCredentials function by creating client and calling refresh
      const client = new QwenOAuth2Client();
      client.setCredentials({
        refresh_token: 'test-refresh',
      });

      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-token',
          token_type: 'Bearer',
          expires_in: 3600,
        }),
      };

      global.fetch = vi.fn().mockResolvedValue(mockResponse as Response);

      await client.refreshAccessToken();

      // Note: File caching is now handled by SharedTokenManager, so these calls won't happen
      // This test verifies that refreshAccessToken works correctly
      const updatedCredentials = client.getCredentials();
      expect(updatedCredentials.access_token).toBe('new-token');
    });
  });

  describe('loadCachedQwenCredentials', () => {
    it('should load and validate cached credentials successfully', async () => {
      const { promises: fs } = await import('node:fs');
      const mockCredentials = {
        access_token: 'cached-token',
        refresh_token: 'cached-refresh',
        token_type: 'Bearer',
        expiry_date: Date.now() + 3600000,
      };

      vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockCredentials));

      // Test through getQwenOAuthClient which calls loadCachedQwenCredentials
      const mockConfig = {
        isBrowserLaunchSuppressed: vi.fn().mockReturnValue(true),
      } as unknown as Config;

      // Make SharedTokenManager fail to test the fallback
      const mockTokenManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('No cached creds')),
      };

      const originalGetInstance = SharedTokenManager.getInstance;
      SharedTokenManager.getInstance = vi
        .fn()
        .mockReturnValue(mockTokenManager);

      // Mock successful auth flow after cache load fails
      const mockAuthResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      const mockTokenResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          refresh_token: 'new-refresh-token',
          token_type: 'Bearer',
          expires_in: 3600,
          scope: 'openid profile email model.completion',
        }),
      };

      global.fetch = vi
        .fn()
        .mockResolvedValueOnce(mockAuthResponse as Response)
        .mockResolvedValue(mockTokenResponse as Response);

      try {
        await import('./qwenOAuth2.js').then((module) =>
          module.getQwenOAuthClient(mockConfig),
        );
      } catch {
        // Expected to fail in test environment
      }

      expect(fs.readFile).toHaveBeenCalled();
      SharedTokenManager.getInstance = originalGetInstance;
    });

    it('should handle invalid cached credentials gracefully', async () => {
      const { promises: fs } = await import('node:fs');

      // Mock file read to return invalid JSON
      vi.mocked(fs.readFile).mockResolvedValue('invalid-json');

      const mockConfig = {
        isBrowserLaunchSuppressed: vi.fn().mockReturnValue(true),
      } as unknown as Config;

      const mockTokenManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('No cached creds')),
      };

      const originalGetInstance = SharedTokenManager.getInstance;
      SharedTokenManager.getInstance = vi
        .fn()
        .mockReturnValue(mockTokenManager);

      // Mock auth flow
      const mockAuthResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      const mockTokenResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-token',
          refresh_token: 'new-refresh',
          token_type: 'Bearer',
          expires_in: 3600,
        }),
      };

      global.fetch = vi
        .fn()
        .mockResolvedValueOnce(mockAuthResponse as Response)
        .mockResolvedValue(mockTokenResponse as Response);

      try {
        await import('./qwenOAuth2.js').then((module) =>
          module.getQwenOAuthClient(mockConfig),
        );
      } catch {
        // Expected to fail in test environment
      }

      SharedTokenManager.getInstance = originalGetInstance;
    });

    it('should handle file access errors', async () => {
      const { promises: fs } = await import('node:fs');

      vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found'));

      const mockConfig = {
        isBrowserLaunchSuppressed: vi.fn().mockReturnValue(true),
      } as unknown as Config;

      const mockTokenManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('No cached creds')),
      };

      const originalGetInstance = SharedTokenManager.getInstance;
      SharedTokenManager.getInstance = vi
        .fn()
        .mockReturnValue(mockTokenManager);

      // Mock device flow to fail quickly
      const mockAuthResponse = {
        ok: true,
        json: async () => ({
          error: 'invalid_request',
          error_description: 'Invalid request parameters',
        }),
      };

      global.fetch = vi.fn().mockResolvedValue(mockAuthResponse as Response);

      // Should proceed to device flow when cache loading fails
      try {
        await import('./qwenOAuth2.js').then((module) =>
          module.getQwenOAuthClient(mockConfig),
        );
      } catch {
        // Expected to fail in test environment
      }

      SharedTokenManager.getInstance = originalGetInstance;
    });
  });
});

describe('Enhanced Error Handling and Edge Cases', () => {
  let client: QwenOAuth2Client;
  let originalFetch: typeof global.fetch;

  beforeEach(() => {
    client = new QwenOAuth2Client();
    originalFetch = global.fetch;
    global.fetch = vi.fn();
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.clearAllMocks();
  });

  describe('QwenOAuth2Client getAccessToken enhanced scenarios', () => {
    it('should return undefined when SharedTokenManager fails (no fallback)', async () => {
      // Set up client with valid credentials (but we don't use fallback anymore)
      client.setCredentials({
        access_token: 'fallback-token',
        expiry_date: Date.now() + 3600000, // Valid for 1 hour
      });

      // Override the client's SharedTokenManager instance directly to ensure it fails
      (
        client as unknown as {
          sharedManager: {
            getValidCredentials: () => Promise<QwenCredentials>;
          };
        }
      ).sharedManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('Manager failed')),
      };

      // Mock console.warn to avoid test noise
      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

      const result = await client.getAccessToken();

      // With our race condition fix, we no longer fall back to local credentials
      // to ensure single source of truth
      expect(result.token).toBeUndefined();
      expect(consoleSpy).toHaveBeenCalledWith(
        'Failed to get access token from shared manager:',
        expect.any(Error),
      );

      consoleSpy.mockRestore();
    });

    it('should return undefined when both manager and cache fail', async () => {
      // Set up client with expired credentials
      client.setCredentials({
        access_token: 'expired-token',
        expiry_date: Date.now() - 1000, // Expired
      });

      // Override the client's SharedTokenManager instance directly to ensure it fails
      (
        client as unknown as {
          sharedManager: {
            getValidCredentials: () => Promise<QwenCredentials>;
          };
        }
      ).sharedManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('Manager failed')),
      };

      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

      const result = await client.getAccessToken();

      expect(result.token).toBeUndefined();

      consoleSpy.mockRestore();
    });

    it('should handle missing credentials gracefully', async () => {
      // No credentials set
      client.setCredentials({});

      // Override the client's SharedTokenManager instance directly to ensure it fails
      (
        client as unknown as {
          sharedManager: {
            getValidCredentials: () => Promise<QwenCredentials>;
          };
        }
      ).sharedManager = {
        getValidCredentials: vi
          .fn()
          .mockRejectedValue(new Error('No credentials')),
      };

      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

      const result = await client.getAccessToken();

      expect(result.token).toBeUndefined();

      consoleSpy.mockRestore();
    });
  });

  describe('Enhanced requestDeviceAuthorization scenarios', () => {
    it('should include x-request-id header', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.requestDeviceAuthorization({
        scope: 'openid profile email model.completion',
        code_challenge: 'test-challenge',
        code_challenge_method: 'S256',
      });

      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          headers: expect.objectContaining({
            'x-request-id': expect.any(String),
          }),
        }),
      );
    });

    it('should include correct Content-Type and Accept headers', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.requestDeviceAuthorization({
        scope: 'openid profile email model.completion',
        code_challenge: 'test-challenge',
        code_challenge_method: 'S256',
      });

      expect(global.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          headers: expect.objectContaining({
            'Content-Type': 'application/x-www-form-urlencoded',
            Accept: 'application/json',
          }),
        }),
      );
    });

    it('should send correct form data', async () => {
      const mockResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.requestDeviceAuthorization({
        scope: 'test-scope',
        code_challenge: 'test-challenge',
        code_challenge_method: 'S256',
      });

      const [, options] = vi.mocked(global.fetch).mock.calls[0];
      expect(options?.body).toContain(
        'client_id=f0304373b74a44d2b584a3fb70ca9e56',
      );
      expect(options?.body).toContain('scope=test-scope');
      expect(options?.body).toContain('code_challenge=test-challenge');
      expect(options?.body).toContain('code_challenge_method=S256');
    });
  });

  describe('Enhanced pollDeviceToken scenarios', () => {
    it('should handle JSON parsing error during error response', async () => {
      const mockResponse = {
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
        text: vi.fn().mockResolvedValue('Invalid request format'),
      };

      vi.mocked(global.fetch).mockResolvedValue(
        mockResponse as unknown as Response,
      );

      await expect(
        client.pollDeviceToken({
          device_code: 'test-device-code',
          code_verifier: 'test-verifier',
        }),
      ).rejects.toThrow('Device token poll failed: 400 Bad Request');
    });

    it('should include status code in thrown errors', async () => {
      const mockResponse = {
        ok: false,
        status: 500,
        statusText: 'Internal Server Error',
        json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
        text: vi.fn().mockResolvedValue('Internal server error'),
      };

      global.fetch = vi
        .fn()
        .mockResolvedValue(mockResponse as unknown as Response);

      await expect(
        client.pollDeviceToken({
          device_code: 'test-device-code',
          code_verifier: 'test-verifier',
        }),
      ).rejects.toMatchObject({
        message: expect.stringContaining(
          'Device token poll failed: 500 Internal Server Error',
        ),
        status: 500,
      });
    });

    it('should handle authorization_pending with correct status', async () => {
      const errorData = {
        error: 'authorization_pending',
        error_description: 'Authorization request is pending',
      };
      const mockResponse = {
        ok: false,
        status: 400,
        statusText: 'Bad Request',
        text: vi.fn().mockResolvedValue(JSON.stringify(errorData)),
        json: vi.fn().mockResolvedValue(errorData),
      };

      vi.mocked(global.fetch).mockResolvedValue(
        mockResponse as unknown as Response,
      );

      const result = await client.pollDeviceToken({
        device_code: 'test-device-code',
        code_verifier: 'test-verifier',
      });

      expect(result).toEqual({ status: 'pending' });
    });
  });

  describe('Enhanced refreshAccessToken scenarios', () => {
    it('should call clearQwenCredentials on 400 error', async () => {
      client.setCredentials({
        refresh_token: 'expired-refresh',
      });

      const { promises: fs } = await import('node:fs');
      vi.mocked(fs.unlink).mockResolvedValue(undefined);

      const mockResponse = {
        ok: false,
        status: 400,
        text: async () => 'Bad Request',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(client.refreshAccessToken()).rejects.toThrow(
        "Refresh token expired or invalid. Please use '/auth' to re-authenticate.",
      );

      expect(fs.unlink).toHaveBeenCalled();
    });

    it('should throw CredentialsClearRequiredError on 400 error', async () => {
      const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');

      client.setCredentials({
        refresh_token: 'expired-refresh',
      });

      const { promises: fs } = await import('node:fs');
      vi.mocked(fs.unlink).mockResolvedValue(undefined);

      const mockResponse = {
        ok: false,
        status: 400,
        text: async () => 'Bad Request',
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await expect(client.refreshAccessToken()).rejects.toThrow(
        CredentialsClearRequiredError,
      );

      try {
        await client.refreshAccessToken();
      } catch (error) {
        expect(error).toBeInstanceOf(CredentialsClearRequiredError);
        if (error instanceof CredentialsClearRequiredError) {
          expect(error.originalError).toEqual({
            status: 400,
            response: 'Bad Request',
          });
        }
      }

      expect(fs.unlink).toHaveBeenCalled();
    });

    it('should preserve existing refresh token when new one not provided', async () => {
      const originalRefreshToken = 'original-refresh-token';
      client.setCredentials({
        refresh_token: originalRefreshToken,
      });

      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          token_type: 'Bearer',
          expires_in: 3600,
          // No refresh_token in response
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.refreshAccessToken();

      const credentials = client.getCredentials();
      expect(credentials.refresh_token).toBe(originalRefreshToken);
    });

    it('should include resource_url when provided in response', async () => {
      client.setCredentials({
        refresh_token: 'test-refresh',
      });

      const mockResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-access-token',
          token_type: 'Bearer',
          expires_in: 3600,
          resource_url: 'https://new-resource-url.com',
        }),
      };

      vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);

      await client.refreshAccessToken();

      const credentials = client.getCredentials();
      expect(credentials.resource_url).toBe('https://new-resource-url.com');
    });
  });
});

describe('SharedTokenManager Integration in QwenOAuth2Client', () => {
  let client: QwenOAuth2Client;

  beforeEach(() => {
    client = new QwenOAuth2Client();
  });

  it('should use SharedTokenManager instance in constructor', () => {
    const sharedManager = (
      client as unknown as { sharedManager: MockSharedTokenManager }
    ).sharedManager;
    expect(sharedManager).toBeDefined();
  });

  it('should handle TokenManagerError types correctly in getQwenOAuthClient', async () => {
    const mockConfig = {
      isBrowserLaunchSuppressed: vi.fn().mockReturnValue(true),
    } as unknown as Config;

    // Test different TokenManagerError types
    const tokenErrors = [
      { type: TokenError.NO_REFRESH_TOKEN, message: 'No refresh token' },
      { type: TokenError.REFRESH_FAILED, message: 'Token refresh failed' },
      { type: TokenError.NETWORK_ERROR, message: 'Network error' },
      { type: TokenError.REFRESH_FAILED, message: 'Refresh failed' },
    ];

    for (const errorInfo of tokenErrors) {
      const tokenError = new TokenManagerError(
        errorInfo.type,
        errorInfo.message,
      );

      const mockTokenManager = {
        getValidCredentials: vi.fn().mockRejectedValue(tokenError),
      };

      const originalGetInstance = SharedTokenManager.getInstance;
      SharedTokenManager.getInstance = vi
        .fn()
        .mockReturnValue(mockTokenManager);

      const { promises: fs } = await import('node:fs');
      vi.mocked(fs.readFile).mockRejectedValue(new Error('No cached file'));

      // Mock device flow to succeed
      const mockAuthResponse = {
        ok: true,
        json: async () => ({
          device_code: 'test-device-code',
          user_code: 'TEST123',
          verification_uri: 'https://chat.qwen.ai/device',
          verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
          expires_in: 1800,
        }),
      };

      const mockTokenResponse = {
        ok: true,
        json: async () => ({
          access_token: 'new-token',
          refresh_token: 'new-refresh',
          token_type: 'Bearer',
          expires_in: 3600,
        }),
      };

      global.fetch = vi
        .fn()
        .mockResolvedValueOnce(mockAuthResponse as Response)
        .mockResolvedValue(mockTokenResponse as Response);

      try {
        await import('./qwenOAuth2.js').then((module) =>
          module.getQwenOAuthClient(mockConfig),
        );
      } catch {
        // Expected to fail in test environment
      }

      SharedTokenManager.getInstance = originalGetInstance;
      vi.clearAllMocks();
    }
  });
});

describe('Constants and Configuration', () => {
  it('should have correct OAuth endpoints', async () => {
    // Test that the constants are properly defined by checking they're used in requests
    const client = new QwenOAuth2Client();

    const mockResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    global.fetch = vi.fn().mockResolvedValue(mockResponse as Response);

    await client.requestDeviceAuthorization({
      scope: 'test-scope',
      code_challenge: 'test-challenge',
      code_challenge_method: 'S256',
    });

    const [url] = vi.mocked(global.fetch).mock.calls[0];
    expect(url).toBe('https://chat.qwen.ai/api/v1/oauth2/device/code');
  });

  it('should use correct client ID in requests', async () => {
    const client = new QwenOAuth2Client();

    const mockResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    global.fetch = vi.fn().mockResolvedValue(mockResponse as Response);

    await client.requestDeviceAuthorization({
      scope: 'test-scope',
      code_challenge: 'test-challenge',
      code_challenge_method: 'S256',
    });

    const [, options] = vi.mocked(global.fetch).mock.calls[0];
    expect(options?.body).toContain(
      'client_id=f0304373b74a44d2b584a3fb70ca9e56',
    );
  });

  it('should use correct default scope', async () => {
    // Test the default scope constant by checking it's used in device flow
    const client = new QwenOAuth2Client();

    const mockResponse = {
      ok: true,
      json: async () => ({
        device_code: 'test-device-code',
        user_code: 'TEST123',
        verification_uri: 'https://chat.qwen.ai/device',
        verification_uri_complete: 'https://chat.qwen.ai/device?code=TEST123',
        expires_in: 1800,
      }),
    };

    global.fetch = vi.fn().mockResolvedValue(mockResponse as Response);

    await client.requestDeviceAuthorization({
      scope: 'openid profile email model.completion',
      code_challenge: 'test-challenge',
      code_challenge_method: 'S256',
    });

    const [, options] = vi.mocked(global.fetch).mock.calls[0];
    expect(options?.body).toContain(
      'scope=openid%20profile%20email%20model.completion',
    );
  });
});
