import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { ErpAuthService } from '../../../../src/main/services/erp/erp-auth'
import type { ErpConfig } from '../../../../src/main/types/erp.types'
import { chromium } from 'playwright'

// Mock playwright
vi.mock('playwright', () => ({
  chromium: {
    launch: vi.fn(),
    connect: vi.fn()
  }
}))

// Mock logger
vi.mock('../../../../src/main/services/logger', () => ({
  createLogger: vi.fn(() => ({
    info: vi.fn(),
    debug: vi.fn(),
    warn: vi.fn(),
    error: vi.fn()
  }))
}))

// Mock error context capture
vi.mock('../../../../src/main/services/erp/erp-error-context', () => ({
  capturePageContext: vi.fn().mockResolvedValue({})
}))

// Mock page diagnostics
vi.mock('../../../../src/main/services/erp/page-diagnostics', () => ({
  attachPageDiagnostics: vi.fn(),
  attachContextDiagnostics: vi.fn()
}))

describe('ErpAuthService', () => {
  const testConfig: ErpConfig = {
    url: 'https://test-erp.local',
    username: 'testuser',
    password: 'testpass'
  }

  const mockBrowser = {
    close: vi.fn().mockResolvedValue(undefined),
    newContext: vi.fn()
  }

  const mockContext = {
    close: vi.fn().mockResolvedValue(undefined),
    newPage: vi.fn()
  }

  const mockPage = {
    goto: vi.fn().mockResolvedValue(undefined),
    waitForLoadState: vi.fn().mockResolvedValue(undefined),
    waitForSelector: vi.fn().mockResolvedValue(undefined),
    locator: vi.fn(),
    close: vi.fn().mockResolvedValue(undefined)
  }

  const mockFrame = {
    locator: vi.fn(),
    getByRole: vi.fn(),
    getByText: vi.fn()
  }

  const mockLocator = {
    click: vi.fn().mockResolvedValue(undefined),
    fill: vi.fn().mockResolvedValue(undefined),
    waitFor: vi.fn().mockResolvedValue(undefined),
    isVisible: vi.fn().mockResolvedValue(false),
    contentFrame: vi.fn()
  }

  beforeEach(() => {
    vi.clearAllMocks()
    mockBrowser.newContext.mockResolvedValue(mockContext)
    mockContext.newPage.mockResolvedValue(mockPage)
    mockPage.locator.mockImplementation(() => mockLocator)
    mockLocator.contentFrame.mockResolvedValue(mockFrame)
    mockFrame.locator.mockImplementation(() => mockLocator)
    mockFrame.getByRole.mockImplementation(() => mockLocator)
    mockFrame.getByText.mockImplementation(() => mockLocator)
  })

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

  describe('login()', () => {
    it('should login successfully with valid credentials', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      mockFrame.getByRole.mockImplementation(() => mockLocator)
      mockFrame.getByText.mockImplementation(() => mockLocator)

      const service = new ErpAuthService(testConfig)
      const session = await service.login()

      expect(chromium.launch).toHaveBeenCalledWith(
        expect.objectContaining({
          headless: false,
          slowMo: 100
        })
      )
      expect(session).toBeDefined()
      expect(session.isLoggedIn).toBe(true)
      expect(service.isActive()).toBe(true)
    })

    it('should return existing session if already logged in', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)

      const service = new ErpAuthService(testConfig)
      const firstSession = await service.login()
      const secondSession = await service.login()

      expect(firstSession).toBe(secondSession)
      expect(chromium.launch).toHaveBeenCalledTimes(1)
    })

    it('should use headless=true from config when specified', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)

      const headlessConfig: ErpConfig = {
        ...testConfig,
        headless: true
      }

      const service = new ErpAuthService(headlessConfig)
      await service.login()

      expect(chromium.launch).toHaveBeenCalledWith(
        expect.objectContaining({
          headless: true
        })
      )
    })

    it('should throw error when forwardFrame is not accessible', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      mockLocator.contentFrame.mockResolvedValue(null)

      const service = new ErpAuthService(testConfig)

      await expect(service.login()).rejects.toThrow('Failed to access forwardFrame content frame')
    })

    it('should throw error when username input is not found', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      mockFrame.getByRole.mockImplementationOnce(() => ({
        ...mockLocator,
        fill: vi.fn().mockRejectedValue(new Error('Element not found'))
      }))

      const service = new ErpAuthService(testConfig)

      await expect(service.login()).rejects.toThrow('Failed to find username input')
    })

    it('should throw error when password input is not found', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      mockFrame.getByRole
        .mockImplementationOnce(() => mockLocator) // username succeeds
        .mockImplementationOnce(() => ({
          ...mockLocator,
          fill: vi.fn().mockRejectedValue(new Error('Element not found'))
        }))

      const service = new ErpAuthService(testConfig)

      await expect(service.login()).rejects.toThrow('Failed to find password input')
    })

    it('should throw error when login button click fails', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      mockFrame.getByRole
        .mockImplementationOnce(() => mockLocator) // username
        .mockImplementationOnce(() => mockLocator) // password
        .mockImplementationOnce(() => ({
          ...mockLocator,
          click: vi.fn().mockRejectedValue(new Error('Button not found'))
        }))

      const service = new ErpAuthService(testConfig)

      await expect(service.login()).rejects.toThrow('Failed to click login button')
    })
  })

  describe('waitForLoginResult()', () => {
    it('should detect successful login', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      const successLocator = {
        waitFor: vi.fn().mockResolvedValue(undefined),
        isVisible: vi.fn().mockResolvedValue(true)
      }
      const errorLocator = {
        waitFor: vi.fn().mockRejectedValue(new Error('Timeout')),
        isVisible: vi.fn().mockResolvedValue(false)
      }
      mockFrame.locator.mockReturnValueOnce(successLocator).mockReturnValueOnce(errorLocator)

      const service = new ErpAuthService(testConfig)
      const session = await service.login()

      expect(session.isLoggedIn).toBe(true)
    })

    it('should detect failed login', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      const successLocator = {
        waitFor: vi.fn().mockRejectedValue(new Error('Timeout')),
        isVisible: vi.fn().mockResolvedValue(false)
      }
      const errorLocator = {
        waitFor: vi.fn().mockResolvedValue(undefined),
        isVisible: vi.fn().mockResolvedValue(true)
      }
      mockFrame.locator.mockReturnValueOnce(successLocator).mockReturnValueOnce(errorLocator)

      const service = new ErpAuthService(testConfig)

      await expect(service.login()).rejects.toThrow('名称或密码错误')
    })

    it('should handle force login popup', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)
      const forceLoginButton = {
        waitFor: vi.fn().mockResolvedValue(undefined),
        click: vi.fn().mockResolvedValue(undefined),
        isVisible: vi.fn().mockResolvedValue(false)
      }
      const successLocator = {
        waitFor: vi.fn().mockResolvedValue(undefined),
        isVisible: vi.fn().mockResolvedValue(true)
      }
      const errorLocator = {
        waitFor: vi.fn().mockRejectedValue(new Error('Timeout')),
        isVisible: vi.fn().mockResolvedValue(false)
      }

      mockFrame.locator
        .mockReturnValueOnce(forceLoginButton)
        .mockReturnValueOnce(successLocator)
        .mockReturnValueOnce(errorLocator)
        .mockReturnValueOnce(forceLoginButton)
        .mockReturnValueOnce(successLocator)
        .mockReturnValueOnce(errorLocator)

      const service = new ErpAuthService(testConfig)
      const session = await service.login()

      expect(session.isLoggedIn).toBe(true)
    })
  })

  describe('logout()', () => {
    it('should close browser and clear session', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)

      const service = new ErpAuthService(testConfig)
      await service.login()
      await service.close()

      expect(mockContext.close).toHaveBeenCalled()
      expect(mockBrowser.close).toHaveBeenCalled()
      expect(service.isActive()).toBe(false)
    })

    it('should be no-op if not logged in', async () => {
      const service = new ErpAuthService(testConfig)

      await expect(service.close()).resolves.toBeUndefined()
      expect(mockContext.close).not.toHaveBeenCalled()
      expect(mockBrowser.close).not.toHaveBeenCalled()
    })
  })

  describe('getSession()', () => {
    it('should return current session', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)

      const service = new ErpAuthService(testConfig)
      const session = await service.login()
      const retrievedSession = service.getSession()

      expect(retrievedSession).toBe(session)
    })

    it('should throw error if not logged in', () => {
      const service = new ErpAuthService(testConfig)

      expect(() => service.getSession()).toThrow('Not logged in. Call login() first.')
    })
  })

  describe('isActive()', () => {
    it('should return correct login state', async () => {
      vi.mocked(chromium.launch).mockResolvedValue(mockBrowser as any)

      const service = new ErpAuthService(testConfig)

      // Before login
      expect(service.isActive()).toBe(false)

      // After login
      await service.login()
      expect(service.isActive()).toBe(true)

      // After logout
      await service.close()
      expect(service.isActive()).toBe(false)
    })
  })
})
