import type { CodexConfigData, CodexProvider } from '../../../../src/utils/code-tools/codex'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
  addProviderToExisting,
  deleteProviders,
  editExistingProvider,
  validateProviderData,
} from '../../../../src/utils/code-tools/codex-provider-manager'

// Mock the codex module functions
vi.mock('../../../../src/utils/code-tools/codex', () => ({
  readCodexConfig: vi.fn(),
  backupCodexComplete: vi.fn(),
  writeAuthFile: vi.fn(),
}))

// Mock the codex-toml-updater module functions (new targeted update approach)
vi.mock('../../../../src/utils/code-tools/codex-toml-updater', () => ({
  updateCodexApiFields: vi.fn(),
  upsertCodexProvider: vi.fn(),
  deleteCodexProvider: vi.fn(),
}))

vi.mock('../../../../src/i18n', () => ({
  ensureI18nInitialized: vi.fn(),
  i18n: {
    t: vi.fn((key: string) => key),
  },
}))

describe('codex-provider-manager', () => {
  beforeEach(() => {
    vi.resetAllMocks()
  })

  const mockExistingConfig: CodexConfigData = {
    model: null,
    modelProvider: 'existing-provider',
    providers: [
      {
        id: 'existing-provider',
        name: 'Existing Provider',
        baseUrl: 'https://api.existing.com/v1',
        wireApi: 'responses',
        tempEnvKey: 'EXISTING_API_KEY',
        requiresOpenaiAuth: true,
      },
    ],
    mcpServices: [],
    managed: true,
    otherConfig: [],
  }

  const mockNewProvider: CodexProvider = {
    id: 'new-provider',
    name: 'New Provider',
    baseUrl: 'https://api.new.com/v1',
    wireApi: 'chat',
    tempEnvKey: 'NEW_API_KEY',
    requiresOpenaiAuth: true,
  }

  describe('addProviderToExisting', () => {
    it('should successfully add new provider to existing configuration', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
        writeAuthFile,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      // Act
      const result = await addProviderToExisting(mockNewProvider, 'new-api-key-value')

      // Assert
      expect(backupCodexComplete).toHaveBeenCalledOnce()
      // New implementation uses targeted upsertCodexProvider instead of writeCodexConfig
      expect(upsertCodexProvider).toHaveBeenCalledWith(mockNewProvider.id, mockNewProvider)
      expect(writeAuthFile).toHaveBeenCalledWith({
        [mockNewProvider.tempEnvKey]: 'new-api-key-value',
      })
      expect(result).toEqual({
        success: true,
        backupPath: '/backup/path/config.toml',
        addedProvider: mockNewProvider,
      })
    })

    it('should reject duplicate provider IDs', async () => {
      // Arrange
      const {
        readCodexConfig,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))

      readCodexConfig.mockReturnValue(mockExistingConfig)

      const duplicateProvider: CodexProvider = {
        ...mockNewProvider,
        id: 'existing-provider', // Same as existing
      }

      // Act
      const result = await addProviderToExisting(duplicateProvider, 'api-key')

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.providerExists',
      })
    })

    it('should overwrite existing provider when allowOverwrite is true', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      const duplicateProvider: CodexProvider = {
        ...mockNewProvider,
        id: 'existing-provider', // Same as existing
        name: 'Updated Provider Name',
      }

      // Act
      const result = await addProviderToExisting(duplicateProvider, 'api-key', true)

      // Assert
      expect(result.success).toBe(true)
      // New implementation uses upsertCodexProvider for targeted updates
      expect(upsertCodexProvider).toHaveBeenCalledWith('existing-provider', duplicateProvider)
    })

    it('should use provider.id as modelProvider when existingConfig.modelProvider is null during overwrite', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        updateCodexApiFields,
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      // Config with null modelProvider
      const configWithNullModelProvider: CodexConfigData = {
        model: null,
        modelProvider: null, // null modelProvider
        providers: [
          {
            id: 'existing-provider',
            name: 'Existing Provider',
            baseUrl: 'https://api.existing.com/v1',
            wireApi: 'responses',
            tempEnvKey: 'EXISTING_API_KEY',
            requiresOpenaiAuth: true,
          },
        ],
        mcpServices: [],
        managed: true,
        otherConfig: [],
      }

      readCodexConfig.mockReturnValue(configWithNullModelProvider)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      const duplicateProvider: CodexProvider = {
        ...mockNewProvider,
        id: 'existing-provider', // Same as existing
        name: 'Updated Provider Name',
      }

      // Act
      const result = await addProviderToExisting(duplicateProvider, 'api-key', true)

      // Assert
      expect(result.success).toBe(true)
      // When existingConfig.modelProvider is null, updateCodexApiFields should set it
      expect(updateCodexApiFields).toHaveBeenCalledWith(expect.objectContaining({
        modelProvider: 'existing-provider',
      }))
      expect(upsertCodexProvider).toHaveBeenCalledWith('existing-provider', duplicateProvider)
    })

    it('should create new configuration when none exists', async () => {
      // Arrange
      const { readCodexConfig, backupCodexComplete } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        updateCodexApiFields,
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))
      readCodexConfig.mockReturnValue(null)
      backupCodexComplete.mockReturnValue(null) // No backup needed for new config

      // Act
      const result = await addProviderToExisting(mockNewProvider, 'api-key')

      // Assert - PR #251 changed behavior: creates new config when none exists
      expect(result.success).toBe(true)
      expect(result.addedProvider).toEqual(mockNewProvider)
      expect(result.backupPath).toBeUndefined() // No backup for new config
      // New implementation uses updateCodexApiFields and upsertCodexProvider
      expect(updateCodexApiFields).toHaveBeenCalledWith(expect.objectContaining({
        model: mockNewProvider.model,
        modelProvider: mockNewProvider.id,
      }))
      expect(upsertCodexProvider).toHaveBeenCalledWith(mockNewProvider.id, mockNewProvider)
    })

    it('should handle backup creation failure', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue(null) // Backup failed

      // Act
      const result = await addProviderToExisting(mockNewProvider, 'api-key')

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.backupFailed',
      })
    })

    it('should handle exceptions during operation', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path')
      upsertCodexProvider.mockImplementation(() => {
        throw new Error('Write failed')
      })

      // Act
      const result = await addProviderToExisting(mockNewProvider, 'api-key')

      // Assert
      expect(result.success).toBe(false)
      expect(result.error).toBe('Write failed')
    })
  })

  describe('editExistingProvider', () => {
    it('should successfully edit existing provider', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
        writeAuthFile,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      const updates = {
        name: 'Updated Provider Name',
        baseUrl: 'https://api.updated.com/v1',
        wireApi: 'responses' as const,
        apiKey: 'updated-api-key',
      }

      // Act
      const result = await editExistingProvider('existing-provider', updates)

      // Assert
      expect(backupCodexComplete).toHaveBeenCalledOnce()
      // New implementation uses upsertCodexProvider for targeted updates
      expect(upsertCodexProvider).toHaveBeenCalledWith('existing-provider', {
        ...mockExistingConfig.providers[0],
        name: updates.name,
        baseUrl: updates.baseUrl,
        wireApi: updates.wireApi,
      })
      expect(writeAuthFile).toHaveBeenCalledWith({
        [mockExistingConfig.providers[0].tempEnvKey]: updates.apiKey,
      })
      expect(result).toEqual({
        success: true,
        backupPath: '/backup/path/config.toml',
        updatedProvider: {
          ...mockExistingConfig.providers[0],
          name: updates.name,
          baseUrl: updates.baseUrl,
          wireApi: updates.wireApi,
        },
      })
    })

    it('should reject edit for non-existent provider', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(mockExistingConfig)

      // Act
      const result = await editExistingProvider('non-existent-provider', {
        name: 'Updated Name',
      })

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.providerNotFound',
      })
    })

    it('should handle partial updates correctly', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      const partialUpdates = {
        name: 'Partially Updated Name',
      }

      // Act
      const result = await editExistingProvider('existing-provider', partialUpdates)

      // Assert
      // New implementation uses upsertCodexProvider for targeted updates
      expect(upsertCodexProvider).toHaveBeenCalledWith('existing-provider', {
        ...mockExistingConfig.providers[0],
        name: partialUpdates.name,
      })
      expect(result.success).toBe(true)
    })

    it('should return error when no config exists', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(null)

      // Act
      const result = await editExistingProvider('existing-provider', { name: 'New Name' })

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.noConfig',
      })
    })

    it('should return error when backup fails', async () => {
      // Arrange
      const { readCodexConfig, backupCodexComplete } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue(null)

      // Act
      const result = await editExistingProvider('existing-provider', { name: 'New Name' })

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.backupFailed',
      })
    })

    it('should update model field when provided', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      const updates = {
        model: 'gpt-5-turbo',
      }

      // Act
      const result = await editExistingProvider('existing-provider', updates)

      // Assert
      expect(result.success).toBe(true)
      // New implementation uses upsertCodexProvider for targeted updates
      expect(upsertCodexProvider).toHaveBeenCalledWith('existing-provider', expect.objectContaining({
        model: 'gpt-5-turbo',
      }))
    })

    it('should handle exceptions during operation', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        upsertCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(mockExistingConfig)
      backupCodexComplete.mockReturnValue('/backup/path')
      upsertCodexProvider.mockImplementation(() => {
        throw new Error('Write operation failed')
      })

      // Act
      const result = await editExistingProvider('existing-provider', { name: 'New Name' })

      // Assert
      expect(result.success).toBe(false)
      expect(result.error).toBe('Write operation failed')
    })
  })

  describe('deleteProviders', () => {
    const multiProviderConfig: CodexConfigData = {
      model: null,
      modelProvider: 'provider-1',
      providers: [
        {
          id: 'provider-1',
          name: 'Provider 1',
          baseUrl: 'https://api.provider1.com/v1',
          wireApi: 'responses',
          tempEnvKey: 'PROVIDER1_API_KEY',
          requiresOpenaiAuth: true,
        },
        {
          id: 'provider-2',
          name: 'Provider 2',
          baseUrl: 'https://api.provider2.com/v1',
          wireApi: 'chat',
          tempEnvKey: 'PROVIDER2_API_KEY',
          requiresOpenaiAuth: true,
        },
        {
          id: 'provider-3',
          name: 'Provider 3',
          baseUrl: 'https://api.provider3.com/v1',
          wireApi: 'responses',
          tempEnvKey: 'PROVIDER3_API_KEY',
          requiresOpenaiAuth: true,
        },
      ],
      mcpServices: [],
      managed: true,
      otherConfig: [],
    }

    it('should successfully delete selected providers', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        deleteCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(multiProviderConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      // Act
      const result = await deleteProviders(['provider-2', 'provider-3'])

      // Assert
      expect(backupCodexComplete).toHaveBeenCalledOnce()
      // New implementation uses deleteCodexProvider for targeted deletion
      expect(deleteCodexProvider).toHaveBeenCalledWith('provider-2')
      expect(deleteCodexProvider).toHaveBeenCalledWith('provider-3')
      expect(result).toEqual({
        success: true,
        backupPath: '/backup/path/config.toml',
        deletedProviders: ['provider-2', 'provider-3'],
        remainingProviders: [multiProviderConfig.providers[0]],
      })
    })

    it('should update default provider when current default is deleted', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        updateCodexApiFields,
        deleteCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(multiProviderConfig)
      backupCodexComplete.mockReturnValue('/backup/path/config.toml')

      // Act - Delete the current default provider (provider-1)
      const result = await deleteProviders(['provider-1'])

      // Assert
      // New implementation uses updateCodexApiFields and deleteCodexProvider
      expect(updateCodexApiFields).toHaveBeenCalledWith(expect.objectContaining({
        modelProvider: 'provider-2', // Should auto-select next available
      }))
      expect(deleteCodexProvider).toHaveBeenCalledWith('provider-1')
      expect(result).toEqual({
        success: true,
        backupPath: '/backup/path/config.toml',
        deletedProviders: ['provider-1'],
        remainingProviders: [
          multiProviderConfig.providers[1],
          multiProviderConfig.providers[2],
        ],
        newDefaultProvider: 'provider-2',
      })
    })

    it('should prevent deletion of all providers', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(multiProviderConfig)

      // Act - Try to delete all providers
      const result = await deleteProviders(['provider-1', 'provider-2', 'provider-3'])

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.cannotDeleteAll',
      })
    })

    it('should handle deletion of non-existent providers', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(multiProviderConfig)

      // Act
      const result = await deleteProviders(['non-existent-provider', 'provider-2'])

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.providersNotFound',
      })
    })

    it('should handle empty deletion list', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(multiProviderConfig)

      // Act
      const result = await deleteProviders([])

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.noProvidersSpecified',
      })
    })

    it('should return error when no config exists', async () => {
      // Arrange
      const { readCodexConfig } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(null)

      // Act
      const result = await deleteProviders(['provider-1'])

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.noConfig',
      })
    })

    it('should return error when backup fails', async () => {
      // Arrange
      const { readCodexConfig, backupCodexComplete } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      readCodexConfig.mockReturnValue(multiProviderConfig)
      backupCodexComplete.mockReturnValue(null)

      // Act
      const result = await deleteProviders(['provider-2'])

      // Assert
      expect(result).toEqual({
        success: false,
        error: 'codex:providerManager.backupFailed',
      })
    })

    it('should handle exceptions during operation', async () => {
      // Arrange
      const {
        readCodexConfig,
        backupCodexComplete,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex'))
      const {
        deleteCodexProvider,
      } = vi.mocked(await import('../../../../src/utils/code-tools/codex-toml-updater'))

      readCodexConfig.mockReturnValue(multiProviderConfig)
      backupCodexComplete.mockReturnValue('/backup/path')
      deleteCodexProvider.mockImplementation(() => {
        throw new Error('Delete operation failed')
      })

      // Act
      const result = await deleteProviders(['provider-2'])

      // Assert
      expect(result.success).toBe(false)
      expect(result.error).toBe('Delete operation failed')
    })
  })

  describe('validateProviderData', () => {
    it('should return valid for complete provider data', () => {
      const validProvider: CodexProvider = {
        id: 'test-provider',
        name: 'Test Provider',
        baseUrl: 'https://api.test.com/v1',
        wireApi: 'responses',
        tempEnvKey: 'TEST_API_KEY',
        requiresOpenaiAuth: true,
      }

      const result = validateProviderData(validProvider)

      expect(result).toEqual({
        valid: true,
        errors: [],
      })
    })

    it('should return errors for missing required fields', () => {
      const incompleteProvider = {}

      const result = validateProviderData(incompleteProvider)

      expect(result.valid).toBe(false)
      expect(result.errors).toEqual([
        'codex:providerManager.providerIdRequired',
        'codex:providerManager.providerNameRequired',
        'codex:providerManager.baseUrlRequired',
      ])
    })

    it('should return error for empty string fields', () => {
      const emptyFieldsProvider = {
        id: '',
        name: '   ',
        baseUrl: '',
      }

      const result = validateProviderData(emptyFieldsProvider)

      expect(result.valid).toBe(false)
      expect(result.errors).toEqual([
        'codex:providerManager.providerIdRequired',
        'codex:providerManager.providerNameRequired',
        'codex:providerManager.baseUrlRequired',
      ])
    })

    it('should return error for invalid wireApi value', () => {
      const invalidWireApiProvider = {
        id: 'test-provider',
        name: 'Test Provider',
        baseUrl: 'https://api.test.com/v1',
        wireApi: 'invalid-protocol',
      }

      const result = validateProviderData(invalidWireApiProvider)

      expect(result.valid).toBe(false)
      expect(result.errors).toEqual([
        'codex:providerManager.wireApiInvalid',
      ])
    })

    it('should accept valid wireApi values', () => {
      const responsesProvider = {
        id: 'test-provider',
        name: 'Test Provider',
        baseUrl: 'https://api.test.com/v1',
        wireApi: 'responses',
      }

      expect(validateProviderData(responsesProvider).valid).toBe(true)
    })

    it('should reject invalid wireApi values', () => {
      const chatProvider = {
        id: 'test-provider',
        name: 'Test Provider',
        baseUrl: 'https://api.test.com/v1',
        wireApi: 'chat', // chat is no longer supported
      }

      expect(validateProviderData(chatProvider).valid).toBe(false)
    })

    it('should handle partial provider data correctly', () => {
      const partialProvider = {
        id: 'test-provider',
        baseUrl: 'https://api.test.com/v1',
        // Missing name
      }

      const result = validateProviderData(partialProvider)

      expect(result.valid).toBe(false)
      expect(result.errors).toEqual(['codex:providerManager.providerNameRequired'])
    })
  })
})
