import { describe, it, expect, beforeEach } from 'vitest'
import { SmartExtractor } from '../../../src/neural/SmartExtractor.js'
import { NounType } from '../../../src/types/graphTypes.js'
import type { Brainy } from '../../../src/brainy.js'

// Mock brain instance
const mockBrain = {
  embed: async (text: string) => {
    return new Array(384).fill(0)
  }
} as unknown as Brainy

describe('SmartExtractor', () => {
  let extractor: SmartExtractor

  beforeEach(() => {
    extractor = new SmartExtractor(mockBrain)
  })

  describe('initialization', () => {
    it('should initialize with default options', () => {
      const extractor = new SmartExtractor(mockBrain)
      const stats = extractor.getStats()

      expect(stats.calls).toBe(0)
      expect(stats.cacheSize).toBe(0)
    })

    it('should initialize with custom options', () => {
      const extractor = new SmartExtractor(mockBrain, {
        minConfidence: 0.70,
        enableFormatHints: false,
        enableEnsemble: true,
        cacheSize: 5000
      })

      const stats = extractor.getStats()
      expect(stats.calls).toBe(0)
    })

    it('should validate signal weights sum to 1.0', () => {
      expect(() => {
        new SmartExtractor(mockBrain, {
          weights: {
            exactMatch: 0.50,
            embedding: 0.30,
            pattern: 0.10,
            context: 0.05 // Sum = 0.95, should error
          }
        })
      }).toThrow('Signal weights must sum to 1.0')
    })

    it('should accept valid custom weights', () => {
      const extractor = new SmartExtractor(mockBrain, {
        weights: {
          exactMatch: 0.30,
          embedding: 0.30,
          pattern: 0.30,
          context: 0.10
        }
      })

      const stats = extractor.getStats()
      expect(stats.calls).toBe(0)
    })
  })

  describe('basic extraction', () => {
    it('should extract person type from title', async () => {
      const result = await extractor.extract('Dr. Dr. Sarah Johnson', {
        definition: 'Chief Medical Officer at General Hospital'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.confidence).toBeGreaterThan(0.60)
    })

    it('should extract organization type', async () => {
      const result = await extractor.extract('Microsoft Corporation', {
        definition: 'Technology company founded in 1975'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Organization)
    })

    it('should extract location type', async () => {
      const result = await extractor.extract('Seattle, WA', {
        definition: 'Major city in Pacific Northwest'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Location)
    })

    it('should extract event type', async () => {
      const result = await extractor.extract('Annual Conference 2024', {
        definition: 'Yearly industry gathering held in June'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Event)
    })

    it('should extract concept type', async () => {
      const result = await extractor.extract('machine learning algorithm', {
        definition: 'Computational method for pattern recognition'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Concept)
    })
  })

  describe('format hint extraction - Excel', () => {
    it('should use Excel column header hints', async () => {
      const result = await extractor.extract('Ms. Jennifer Martinez', {
        definition: 'Software engineer on frontend team',
        formatContext: {
          format: 'excel',
          columnHeader: 'Employee Name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.metadata?.formatHints).toBeDefined()
      expect(result?.metadata?.formatHints).toContain('Employee Name')
    })

    it('should extract type keywords from headers', async () => {
      const result = await extractor.extract('Global Tech Inc', {
        definition: 'Software company',
        formatContext: {
          format: 'excel',
          columnHeader: 'Company Name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Organization)
      expect(result?.metadata?.formatHints).toContain('company')
    })

    it('should use sheet name as hint', async () => {
      const result = await extractor.extract('Bob Wilson', {
        definition: 'Team member',
        formatContext: {
          format: 'excel',
          columnHeader: 'Name',
          sheetName: 'Employees'
        }
      })

      expect(result).toBeDefined()
      expect(result?.metadata?.formatHints).toContain('Employees')
    })
  })

  describe('format hint extraction - CSV', () => {
    it('should parse CSV header patterns', async () => {
      const result = await extractor.extract('Dr. Alice Chen', {
        definition: 'Research lead',
        formatContext: {
          format: 'csv',
          columnHeader: 'author_name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.metadata?.formatHints).toContain('author_name')
    })

    it('should split underscore patterns', async () => {
      const result = await extractor.extract('Acme Corp', {
        definition: 'Business entity',
        formatContext: {
          format: 'csv',
          columnHeader: 'company_name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.metadata?.formatHints).toContain('company')
      expect(result?.metadata?.formatHints).toContain('name')
    })
  })

  describe('format hint extraction - YAML', () => {
    it('should use YAML key as hint', async () => {
      const result = await extractor.extract('Dr. John Smith', {
        definition: 'Lead researcher',
        formatContext: {
          format: 'yaml',
          yamlKey: 'author'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.metadata?.formatHints).toContain('author')
    })

    it('should parse hyphenated YAML keys', async () => {
      const result = await extractor.extract('TechVentures Inc', {
        definition: 'Startup company',
        formatContext: {
          format: 'yaml',
          yamlKey: 'company-name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.metadata?.formatHints).toContain('company')
    })
  })

  describe('format hint extraction - PDF', () => {
    it('should extract hints from PDF field names', async () => {
      const result = await extractor.extract('Jane Doe', {
        definition: 'Applicant',
        formatContext: {
          format: 'pdf',
          fieldName: 'applicant_name'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.metadata?.formatHints).toContain('applicant_name')
    })

    it('should parse camelCase field names', async () => {
      const result = await extractor.extract('Boston, MA', {
        definition: 'City location',
        formatContext: {
          format: 'pdf',
          fieldName: 'cityName'
        }
      })

      expect(result).toBeDefined()
      expect(result?.metadata?.formatHints).toContain('city')
    })
  })

  describe('format hint extraction - DOCX', () => {
    it('should use heading level hints', async () => {
      const result = await extractor.extract('Project Phoenix', {
        definition: 'Digital transformation initiative',
        formatContext: {
          format: 'docx',
          headingLevel: 1
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Project)
      expect(result?.metadata?.formatHints).toBeDefined()
    })
  })

  describe('ensemble voting', () => {
    it('should combine multiple signals', async () => {
      extractor.resetStats()

      const result = await extractor.extract('CEO Dr. Sarah Johnson', {
        definition: 'Chief executive officer and board member since 2018'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.source).toBe('ensemble')

      // Should have multiple signals agreeing
      expect(result?.metadata?.signalResults).toBeDefined()
      expect(result?.metadata?.signalResults!.length).toBeGreaterThan(1)
    })

    it('should apply agreement boost', async () => {
      const result = await extractor.extract('Dr. Emily Chen', {
        definition: 'Medical researcher and professor at Stanford University'
      })

      expect(result).toBeDefined()
      if (result?.metadata?.signalResults && result.metadata.signalResults.length > 1) {
        expect(result.metadata.agreementBoost).toBeGreaterThan(0)
      }
    })

    it('should respect minimum confidence threshold', async () => {
      const strictExtractor = new SmartExtractor(mockBrain, {
        minConfidence: 0.95
      })

      const result = await strictExtractor.extract('ambiguous entity', {
        definition: 'Not clear what this is'
      })

      // High threshold should filter out low-confidence results
      if (result) {
        expect(result.confidence).toBeGreaterThanOrEqual(0.95)
      }
    })
  })

  describe('single signal mode', () => {
    it('should use best signal when ensemble disabled', async () => {
      const singleSignalExtractor = new SmartExtractor(mockBrain, {
        enableEnsemble: false
      })

      const result = await singleSignalExtractor.extract('Dr. Smith', {
        definition: 'Medical professional'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(['exact-match', 'pattern', 'embedding', 'context']).toContain(result?.source)
    })
  })

  describe('statistics tracking', () => {
    it('should track call statistics', async () => {
      extractor.resetStats()

      await extractor.extract('Entity 1', { definition: 'Test' })
      await extractor.extract('Entity 2', { definition: 'Test' })

      const stats = extractor.getStats()
      expect(stats.calls).toBe(2)
    })

    it('should track cache hits', async () => {
      extractor.resetStats()

      await extractor.extract('Dr. Test', { definition: 'Doctor' })
      await extractor.extract('Dr. Test', { definition: 'Doctor' })

      const stats = extractor.getStats()
      expect(stats.cacheHits).toBe(1)
      expect(stats.cacheHitRate).toBeGreaterThan(0)
    })

    it('should track signal wins', async () => {
      extractor.resetStats()

      // This should trigger exact match or pattern
      await extractor.extract('Microsoft Corporation', {
        definition: 'Software company'
      })

      const stats = extractor.getStats()
      const totalWins = stats.exactMatchWins + stats.patternWins +
                        stats.embeddingWins + stats.contextWins + stats.ensembleWins
      expect(totalWins).toBeGreaterThan(0)
    })

    it('should calculate average confidence', async () => {
      extractor.resetStats()

      await extractor.extract('Dr. Smith', { definition: 'Doctor' })
      await extractor.extract('Acme Corp', { definition: 'Company' })

      const stats = extractor.getStats()
      expect(stats.averageConfidence).toBeGreaterThan(0)
      expect(stats.averageConfidence).toBeLessThanOrEqual(1)
    })

    it('should track format hint usage', async () => {
      extractor.resetStats()

      await extractor.extract('Test Entity', {
        definition: 'Test',
        formatContext: {
          format: 'excel',
          columnHeader: 'Name'
        }
      })

      const stats = extractor.getStats()
      expect(stats.formatHintsUsed).toBeGreaterThanOrEqual(1)
    })

    it('should provide signal-level statistics', () => {
      const stats = extractor.getStats()

      expect(stats.signalStats).toBeDefined()
      expect(stats.signalStats.exactMatch).toBeDefined()
      expect(stats.signalStats.pattern).toBeDefined()
      expect(stats.signalStats.embedding).toBeDefined()
      expect(stats.signalStats.context).toBeDefined()
    })

    it('should reset all statistics', async () => {
      await extractor.extract('Test', { definition: 'Test' })

      extractor.resetStats()

      const stats = extractor.getStats()
      expect(stats.calls).toBe(0)
      expect(stats.cacheHits).toBe(0)
      expect(stats.averageConfidence).toBe(0)
    })
  })

  describe('cache operations', () => {
    it('should cache extraction results', async () => {
      const result1 = await extractor.extract('Dr. Test', {
        definition: 'Medical professional'
      })

      const result2 = await extractor.extract('Dr. Test', {
        definition: 'Medical professional'
      })

      expect(result1).toEqual(result2)
    })

    it('should clear all caches', async () => {
      await extractor.extract('Dr. Test', { definition: 'Doctor' })

      extractor.clearCache()

      const stats = extractor.getStats()
      expect(stats.cacheSize).toBe(0)
    })

    it('should respect cache size limit', async () => {
      const smallCacheExtractor = new SmartExtractor(mockBrain, {
        cacheSize: 10
      })

      // Add more than cache size
      for (let i = 0; i < 20; i++) {
        await smallCacheExtractor.extract(`Entity ${i}`, {
          definition: `Test entity ${i}`
        })
      }

      const stats = smallCacheExtractor.getStats()
      expect(stats.cacheSize).toBeLessThanOrEqual(10)
    })
  })

  describe('real-world scenarios', () => {
    it('should classify employee from HR spreadsheet', async () => {
      const result = await extractor.extract('Ms. Jennifer Martinez', {
        definition: 'Software Engineer, Frontend Team, employed since 2020',
        formatContext: {
          format: 'excel',
          columnHeader: 'Employee Name',
          sheetName: 'Staff Directory'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
      expect(result?.confidence).toBeGreaterThan(0.70)
    })

    it('should classify company from business database', async () => {
      const result = await extractor.extract('Global Innovations Inc', {
        definition: 'Fortune 500 technology company founded in 1998',
        formatContext: {
          format: 'csv',
          columnHeader: 'company_name'
        },
        metadata: {
          industry: 'Technology',
          founded: 1998
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Organization)
    })

    it('should classify location from travel itinerary', async () => {
      const result = await extractor.extract('Tokyo, Japan', {
        definition: 'Meeting location for Q2 2024 conference',
        formatContext: {
          format: 'docx',
          headingLevel: 2
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Location)
    })

    it('should classify event from conference program', async () => {
      const result = await extractor.extract('DevConf 2024', {
        definition: 'Annual developer conference held in June',
        formatContext: {
          format: 'yaml',
          yamlKey: 'event-name'
        },
        metadata: {
          date: '2024-06-15',
          venue: 'Convention Center'
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Event)
    })

    it('should classify project from roadmap', async () => {
      const result = await extractor.extract('Project Phoenix', {
        definition: 'Digital transformation initiative launched Q1 2024',
        formatContext: {
          format: 'markdown',
          headingLevel: 1
        }
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Project)
    })

    it('should classify concept from knowledge base', async () => {
      const result = await extractor.extract('microservices architecture', {
        definition: 'Design pattern for building distributed systems using independent services',
        formatContext: {
          format: 'markdown',
          headingLevel: 2
        },
        allTerms: ['architecture', 'pattern', 'distributed', 'design']
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Concept)
    })
  })

  describe('edge cases', () => {
    it('should handle entities without context', async () => {
      const result = await extractor.extract('Microsoft Corporation')

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Organization)
    })

    it('should handle entities with minimal definition', async () => {
      const result = await extractor.extract('Dr. Smith', {
        definition: 'Medical professional'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Person)
    })

    it('should handle format hints disabled', async () => {
      const noHintsExtractor = new SmartExtractor(mockBrain, {
        enableFormatHints: false
      })

      const result = await noHintsExtractor.extract('Test Entity', {
        definition: 'Test',
        formatContext: {
          format: 'excel',
          columnHeader: 'Name'
        }
      })

      if (result) {
        expect(result.metadata?.formatHints).toBeUndefined()
      }
    })

    it('should handle null results gracefully', async () => {
      const result = await extractor.extract('xyzabc', {
        definition: 'Completely ambiguous gibberish with no patterns'
      })

      // May return null if confidence too low
      if (!result) {
        expect(result).toBeNull()
      }
    })

    it('should handle special characters', async () => {
      const result = await extractor.extract('C++', {
        definition: 'Programming language developed by Bjarne Stroustrup'
      })

      expect(result).toBeDefined()
      expect(result?.type).toBe(NounType.Thing)
    })
  })

  describe('history management', () => {
    it('should add entities to history', () => {
      const vector = new Array(384).fill(0.1)

      extractor.addToHistory('Test Entity', NounType.Person, vector)

      // History is internal, just ensure no errors
      expect(true).toBe(true)
    })

    it('should clear history', () => {
      const vector = new Array(384).fill(0.1)
      extractor.addToHistory('Test Entity', NounType.Person, vector)

      extractor.clearHistory()

      // History cleared, no errors
      expect(true).toBe(true)
    })
  })
})
