import { describe, it, expect, beforeEach, vi } from 'vitest'
import { ExtractorService } from '../../../../src/main/services/erp/extractor'
import { ErpAuthService } from '../../../../src/main/services/erp/erp-auth'
import fs from 'fs/promises'
import type { ExtractorInput, ImportResult } from '../../../../src/main/types/extractor.types'
import type { ExcelParser } from '../../../../src/main/services/excel/excel-parser'
import type { DataImportService } from '../../../../src/main/services/database/data-importer'

// Mock external dependencies
vi.mock('fs/promises', () => ({
  default: {
    mkdir: vi.fn().mockResolvedValue(undefined),
    unlink: vi.fn().mockResolvedValue(undefined)
  },
  mkdir: vi.fn().mockResolvedValue(undefined),
  unlink: vi.fn().mockResolvedValue(undefined)
}))

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

  return {
    default: mockLogger,
    createLogger: vi.fn(() => mockLogger),
    withRequestContext: vi.fn(async (fn) => fn()),
    getRequestId: vi.fn(() => 'test-request-id')
  }
})

vi.mock('../../../../src/main/services/logger/performance-monitor', () => {
  return {
    trackDuration: vi.fn(async (fn) => ({ result: await fn() }))
  }
})

// Mock ExcelParser - reset in beforeEach
let mockExcelParserInstance: any
vi.mock('../../../../src/main/services/excel/excel-parser', () => ({
  ExcelParser: function ExcelParser() {
    return mockExcelParserInstance
  }
}))

// Mock DataImportService - reset in beforeEach
let mockDataImportInstance: any
vi.mock('../../../../src/main/services/database/data-importer', () => ({
  DataImportService: function DataImportService() {
    return mockDataImportInstance
  }
}))

// Mock ExtractorCore - reset in beforeEach
let mockExtractorCoreInstance: any
vi.mock('../../../../src/main/services/erp/extractor-core', () => ({
  ExtractorCore: function ExtractorCore() {
    return mockExtractorCoreInstance
  }
}))

describe('ExtractorService', () => {
  let mockAuthService: ErpAuthService
  let mockSession: any

  beforeEach(() => {
    vi.clearAllMocks()

    mockSession = { cookie: 'test-cookie' }
    mockAuthService = {
      getSession: vi.fn(() => mockSession)
    } as unknown as ErpAuthService

    // Initialize mock instances
    mockExcelParserInstance = {
      parse: vi.fn().mockResolvedValue(undefined),
      _lastOrders: [] as Array<{ orderInfo: any; materials: any[] }>,
      get lastOrders() {
        return this._lastOrders
      },
      set lastOrders(val) {
        this._lastOrders = val
      }
    }

    // Explicitly reset lastOrders
    mockExcelParserInstance.lastOrders = []

    mockDataImportInstance = {
      importFromExcel: vi.fn().mockResolvedValue({
        success: true,
        recordsRead: 0,
        recordsDeleted: 0,
        recordsImported: 0,
        uniqueSourceNumbers: 0,
        errors: []
      } as ImportResult)
    }

    const mockDownloadAllBatches = vi.fn().mockResolvedValue({
      downloadedFiles: [],
      errors: []
    })
    mockExtractorCoreInstance = {
      downloadAllBatches: mockDownloadAllBatches
    }
  })

  // TODO: Complex extract() flow tests need integration test setup

  describe('Constructor', () => {
    it('should create instance with default download dir', () => {
      const service = new ExtractorService(mockAuthService)
      expect(service).toBeInstanceOf(ExtractorService)
    })

    it('should create instance with custom download dir', () => {
      const service = new ExtractorService(mockAuthService, './custom-downloads')
      expect(service).toBeInstanceOf(ExtractorService)
    })

    it('should ensure download directory exists', async () => {
      // Clear fs.mkdir mock history before creating instance
      vi.mocked(fs.mkdir).mockClear()

      // Create instance (constructor calls fs.mkdir asynchronously)
      new ExtractorService(mockAuthService, './test-downloads')

      // Flush microtask queue so constructor's async mkdir resolves
      await new Promise((resolve) => setImmediate(resolve))
      expect(fs.mkdir).toHaveBeenCalledWith('./test-downloads', { recursive: true })
    })
  })

  describe('extract() - Basic Behavior', () => {
    it('should return result object', async () => {
      const service = new ExtractorService(mockAuthService, './test-downloads')
      const input: ExtractorInput = { orderNumbers: [] }

      const result = await service.extract(input)

      expect(result).toBeDefined()
      expect(typeof result).toBe('object')
    })

    it('should handle empty order numbers', async () => {
      const service = new ExtractorService(mockAuthService, './test-downloads')

      await expect(service.extract({ orderNumbers: [] })).resolves.toBeDefined()
    })

    it('should capture errors from ExtractorCore', async () => {
      mockExtractorCoreInstance.downloadAllBatches.mockResolvedValue({
        downloadedFiles: [],
        errors: ['Download failed']
      })

      const service = new ExtractorService(mockAuthService, './test-downloads')
      const result = await service.extract({ orderNumbers: ['ORD001'] })

      expect(result.downloadedFiles).toEqual([])
      expect(result.errors).toContain('Download failed')
    })

    it('should handle extraction errors gracefully', async () => {
      mockExtractorCoreInstance.downloadAllBatches.mockRejectedValue(new Error('Network error'))

      const service = new ExtractorService(mockAuthService, './test-downloads')
      const result = await service.extract({ orderNumbers: ['ORD001'] })

      expect(Array.isArray(result.errors)).toBe(true)
    })
  })

  describe('mergeFiles()', () => {
    it('should return null when no files to merge', async () => {
      const service = new ExtractorService(mockAuthService)

      // @ts-ignore - accessing private method for testing
      const result = await service.mergeFiles([], ['ORD001'])

      expect(result.mergedFile).toBeNull()
      expect(result.recordCount).toBe(0)
      expect(result.orderRecordCounts).toEqual([])
    })

    it('should handle single file', async () => {
      mockExcelParserInstance.lastOrders = [
        {
          orderInfo: { productionOrder: 'ORD001' },
          materials: [{ materialCode: 'MAT001', quantity: 10 }]
        }
      ]

      const service = new ExtractorService(mockAuthService, './test-downloads')

      // @ts-ignore - accessing private method for testing
      const result = await service.mergeFiles(['./file1.xlsx'], ['ORD001'])

      expect(result.recordCount).toBe(1)
      expect(result.orderRecordCounts).toEqual([{ orderNumber: 'ORD001', recordCount: 1 }])
    })

    it('should handle multiple files', async () => {
      // Mock parse to return different data for each file
      let callCount = 0
      mockExcelParserInstance.parse = vi.fn().mockImplementation(() => {
        callCount++
        if (callCount === 1) {
          mockExcelParserInstance._lastOrders = [
            {
              orderInfo: { productionOrder: 'ORD001' },
              materials: [{ materialCode: 'MAT001', quantity: 5 }]
            }
          ]
        } else {
          mockExcelParserInstance._lastOrders = [
            {
              orderInfo: { productionOrder: 'ORD002' },
              materials: [
                { materialCode: 'MAT002', quantity: 10 },
                { materialCode: 'MAT003', quantity: 15 }
              ]
            }
          ]
        }
        return Promise.resolve()
      })

      const service = new ExtractorService(mockAuthService, './test-downloads')

      // @ts-ignore - accessing private method for testing
      const result = await service.mergeFiles(
        ['./file1.xlsx', './file2.xlsx'],
        ['ORD001', 'ORD002']
      )

      expect(result.recordCount).toBe(3)
      expect(result.orderRecordCounts).toHaveLength(2)
      expect(result.orderRecordCounts[0]).toEqual({ orderNumber: 'ORD001', recordCount: 1 })
      expect(result.orderRecordCounts[1]).toEqual({ orderNumber: 'ORD002', recordCount: 2 })
    })
  })

  describe('cleanupTempFiles()', () => {
    it('should delete all temporary files', async () => {
      const service = new ExtractorService(mockAuthService)
      const files = ['./temp1.xlsx', './temp2.xlsx', './temp3.xlsx']

      // @ts-ignore - accessing private method for testing
      await service.cleanupTempFiles(files, ['ORD001'])

      expect(fs.unlink).toHaveBeenCalledTimes(3)
      expect(fs.unlink).toHaveBeenCalledWith('./temp1.xlsx')
      expect(fs.unlink).toHaveBeenCalledWith('./temp2.xlsx')
      expect(fs.unlink).toHaveBeenCalledWith('./temp3.xlsx')
    })

    it('should handle deletion errors gracefully', async () => {
      vi.mocked(fs.unlink).mockRejectedValue(new Error('File not found'))

      const service = new ExtractorService(mockAuthService)
      const files = ['./temp1.xlsx', './temp2.xlsx']

      // @ts-ignore - accessing private method for testing
      await expect(service.cleanupTempFiles(files, ['ORD001'])).resolves.not.toThrow()

      expect(fs.unlink).toHaveBeenCalledTimes(2)
    })
  })

  describe('importToDatabaseWithLogging()', () => {
    it('should return success result', async () => {
      mockDataImportInstance.importFromExcel.mockResolvedValue({
        success: true,
        recordsRead: 100,
        recordsDeleted: 50,
        recordsImported: 50,
        uniqueSourceNumbers: 5,
        errors: []
      })

      const service = new ExtractorService(mockAuthService)
      const onLog = vi.fn()

      // @ts-ignore - accessing private method for testing
      const result = await service.importToDatabaseWithLogging('./merged.xlsx', onLog)

      expect(result.success).toBe(true)
      expect(result.recordsRead).toBe(100)
      expect(result.recordsImported).toBe(50)
      expect(onLog).toHaveBeenCalledWith('success', expect.stringContaining('导入完成'))
    })

    it('should handle import failure', async () => {
      mockDataImportInstance.importFromExcel.mockRejectedValue(
        new Error('Database connection failed')
      )

      const service = new ExtractorService(mockAuthService)
      const onLog = vi.fn()

      // @ts-ignore - accessing private method for testing
      const result = await service.importToDatabaseWithLogging('./merged.xlsx', onLog)

      expect(result.success).toBe(false)
      expect(result.errors.some((e) => e.includes('Database connection failed'))).toBe(true)
      expect(onLog).toHaveBeenCalledWith('error', expect.stringContaining('导入失败'))
    })

    it('should handle import with errors in result', async () => {
      mockDataImportInstance.importFromExcel.mockResolvedValue({
        success: false,
        recordsRead: 50,
        recordsDeleted: 0,
        recordsImported: 0,
        uniqueSourceNumbers: 0,
        errors: ['Validation failed', 'Duplicate records']
      })

      const service = new ExtractorService(mockAuthService)
      const onLog = vi.fn()

      // @ts-ignore - accessing private method for testing
      const result = await service.importToDatabaseWithLogging('./merged.xlsx', onLog)

      expect(result.success).toBe(false)
      expect(result.errors).toEqual(['Validation failed', 'Duplicate records'])
      expect(onLog).toHaveBeenCalledTimes(3)
    })

    it('should wrap import in trackDuration', async () => {
      mockDataImportInstance.importFromExcel.mockResolvedValue({
        success: true,
        recordsRead: 10,
        recordsDeleted: 0,
        recordsImported: 10,
        uniqueSourceNumbers: 1,
        errors: []
      })

      const { trackDuration } =
        await import('../../../../src/main/services/logger/performance-monitor')
      const service = new ExtractorService(mockAuthService)
      const onLog = vi.fn()

      // @ts-ignore - accessing private method for testing
      await service.importToDatabaseWithLogging('./merged.xlsx', onLog)

      expect(trackDuration).toHaveBeenCalledWith(
        expect.any(Function),
        expect.objectContaining({ operationName: 'Database Import' })
      )
    })
  })
})
