import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { ChatGPTClient, getStatusText, getStatusColor, createChatGPTClient } from './chatgpt'
import type { AccountInput } from '../types'

describe('ChatGPTClient', () => {
  let client: ChatGPTClient
  let fetchMock: ReturnType<typeof vi.fn>

  beforeEach(() => {
    client = new ChatGPTClient()
    fetchMock = vi.fn()
    globalThis.fetch = fetchMock as typeof fetch
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  describe('checkAccount', () => {
    it('should return active status for HTTP 200 with account info', async () => {
      const mockResponse = {
        accounts: [
          {
            account_id: 'test-account-id',
            entitlement: {
              subscription_plan: 'plus',
            },
          },
        ],
      }

      fetchMock.mockResolvedValueOnce({
        status: 200,
        statusText: 'OK',
        json: () => Promise.resolve(mockResponse),
      })

      const result = await client.checkAccount('valid-token')

      expect(result.status).toBe('active')
      expect(result.accountId).toBe('test-account-id')
      expect(result.planType).toBe('plus')
      expect(result.error).toBeUndefined()
    })

    it('should return active status for HTTP 200 without account info', async () => {
      const mockResponse = {
        accounts: [],
      }

      fetchMock.mockResolvedValueOnce({
        status: 200,
        statusText: 'OK',
        json: () => Promise.resolve(mockResponse),
      })

      const result = await client.checkAccount('valid-token')

      expect(result.status).toBe('active')
      expect(result.accountId).toBeUndefined()
      expect(result.planType).toBe('unknown')
    })

    it('should return token_expired status for HTTP 401', async () => {
      fetchMock.mockResolvedValueOnce({
        status: 401,
        statusText: 'Unauthorized',
        json: () => Promise.reject(new Error('No JSON')),
      })

      const result = await client.checkAccount('expired-token')

      expect(result.status).toBe('token_expired')
      expect(result.error).toBe('Token 已过期')
    })

    it('should return banned status for HTTP 403', async () => {
      fetchMock.mockResolvedValueOnce({
        status: 403,
        statusText: 'Forbidden',
        json: () => Promise.reject(new Error('No JSON')),
      })

      const result = await client.checkAccount('banned-token')

      expect(result.status).toBe('banned')
      expect(result.error).toBe('账号已被封禁')
    })

    it('should return error status for other HTTP codes', async () => {
      fetchMock.mockResolvedValueOnce({
        status: 500,
        statusText: 'Internal Server Error',
        json: () => Promise.reject(new Error('No JSON')),
      })

      const result = await client.checkAccount('some-token')

      expect(result.status).toBe('error')
      expect(result.error).toBe('HTTP 500: Internal Server Error')
    })

    it('should return error status for network errors', async () => {
      fetchMock.mockRejectedValueOnce(new Error('Network error'))

      const result = await client.checkAccount('some-token')

      expect(result.status).toBe('error')
      expect(result.error).toBe('Network error')
    })

    it('should return error status for empty token', async () => {
      const result = await client.checkAccount('')

      expect(result.status).toBe('error')
      expect(result.error).toBe('缺少 token')
      expect(fetchMock).not.toHaveBeenCalled()
    })

    it('should return error status for whitespace-only token', async () => {
      const result = await client.checkAccount('   ')

      expect(result.status).toBe('error')
      expect(result.error).toBe('缺少 token')
      expect(fetchMock).not.toHaveBeenCalled()
    })

    it('should use correct API endpoint and headers', async () => {
      fetchMock.mockResolvedValueOnce({
        status: 200,
        statusText: 'OK',
        json: () => Promise.resolve({ accounts: [] }),
      })

      await client.checkAccount('test-token')

      expect(fetchMock).toHaveBeenCalledWith('/api/chatgpt/accounts/check/v4-2023-04-27', {
        method: 'GET',
        headers: {
          Authorization: 'Bearer test-token',
          'Content-Type': 'application/json',
        },
      })
    })

    it('should handle JSON parse errors gracefully for HTTP 200', async () => {
      fetchMock.mockResolvedValueOnce({
        status: 200,
        statusText: 'OK',
        json: () => Promise.reject(new Error('Invalid JSON')),
      })

      const result = await client.checkAccount('valid-token')

      // Should still return active since HTTP 200
      expect(result.status).toBe('active')
      expect(result.planType).toBe('unknown')
    })
  })

  describe('batchCheck', () => {
    it('should return empty array for empty input', async () => {
      const results = await client.batchCheck([], { concurrency: 5 })

      expect(results).toEqual([])
      expect(fetchMock).not.toHaveBeenCalled()
    })

    it('should check all accounts and return results in order', async () => {
      const accounts: AccountInput[] = [
        { account: 'user1@test.com', password: 'pass1', token: 'token1' },
        { account: 'user2@test.com', password: 'pass2', token: 'token2' },
        { account: 'user3@test.com', password: 'pass3', token: 'token3' },
      ]

      // Mock responses for each account
      fetchMock
        .mockResolvedValueOnce({
          status: 200,
          statusText: 'OK',
          json: () =>
            Promise.resolve({
              accounts: [{ account_id: 'id1', entitlement: { subscription_plan: 'plus' } }],
            }),
        })
        .mockResolvedValueOnce({
          status: 401,
          statusText: 'Unauthorized',
        })
        .mockResolvedValueOnce({
          status: 403,
          statusText: 'Forbidden',
        })

      const results = await client.batchCheck(accounts, { concurrency: 3 })

      expect(results).toHaveLength(3)
      expect(results[0].status).toBe('active')
      expect(results[0].accountId).toBe('id1')
      expect(results[1].status).toBe('token_expired')
      expect(results[2].status).toBe('banned')
    })

    it('should call onProgress callback for each account', async () => {
      const accounts: AccountInput[] = [
        { account: 'user1@test.com', password: 'pass1', token: 'token1' },
        { account: 'user2@test.com', password: 'pass2', token: 'token2' },
      ]

      fetchMock
        .mockResolvedValueOnce({
          status: 200,
          statusText: 'OK',
          json: () => Promise.resolve({ accounts: [] }),
        })
        .mockResolvedValueOnce({
          status: 200,
          statusText: 'OK',
          json: () => Promise.resolve({ accounts: [] }),
        })

      const onProgress = vi.fn()

      await client.batchCheck(accounts, { concurrency: 2, onProgress })

      expect(onProgress).toHaveBeenCalledTimes(2)
    })

    it('should respect concurrency limit', async () => {
      const accounts: AccountInput[] = Array.from({ length: 10 }, (_, i) => ({
        account: `user${i}@test.com`,
        password: `pass${i}`,
        token: `token${i}`,
      }))

      let maxConcurrent = 0
      let currentConcurrent = 0

      fetchMock.mockImplementation(async () => {
        currentConcurrent++
        maxConcurrent = Math.max(maxConcurrent, currentConcurrent)

        // Simulate network delay
        await new Promise((resolve) => setTimeout(resolve, 10))

        currentConcurrent--
        return {
          status: 200,
          statusText: 'OK',
          json: () => Promise.resolve({ accounts: [] }),
        }
      })

      await client.batchCheck(accounts, { concurrency: 3 })

      // Max concurrent should not exceed the concurrency limit
      expect(maxConcurrent).toBeLessThanOrEqual(3)
    })
  })
})

describe('getStatusText', () => {
  it('should return correct Chinese text for each status', () => {
    expect(getStatusText('pending')).toBe('待检查')
    expect(getStatusText('checking')).toBe('检查中')
    expect(getStatusText('active')).toBe('正常')
    expect(getStatusText('banned')).toBe('封禁')
    expect(getStatusText('token_expired')).toBe('过期')
    expect(getStatusText('error')).toBe('错误')
  })
})

describe('getStatusColor', () => {
  it('should return correct color for each status', () => {
    expect(getStatusColor('pending')).toBe('gray')
    expect(getStatusColor('checking')).toBe('blue')
    expect(getStatusColor('active')).toBe('green')
    expect(getStatusColor('banned')).toBe('red')
    expect(getStatusColor('token_expired')).toBe('orange')
    expect(getStatusColor('error')).toBe('yellow')
  })
})

describe('createChatGPTClient', () => {
  it('should create a new ChatGPTClient instance', () => {
    const client = createChatGPTClient()
    expect(client).toBeInstanceOf(ChatGPTClient)
  })

  it('should create a client with custom base URL', () => {
    const client = createChatGPTClient('https://custom.api.com')
    expect(client).toBeInstanceOf(ChatGPTClient)
  })
})
