/**
 * Tests for Import Resolver — extractAllImports, extractExports, resolveImportPath, buildFileIndex
 */

import {
  extractAllImports,
  extractExports,
  resolveImportPath,
  buildFileIndex,
} from '../../model/import-resolver'
import type { ScanFile } from '../../shared/types'

function makeFile(path: string, content: string = ''): ScanFile {
  return { path, content, language: 'typescript' }
}

// ============================================================================
// extractAllImports
// ============================================================================

describe('extractAllImports', () => {
  it('parses ES6 named imports', () => {
    const content = `import { foo, bar } from './utils'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['foo', 'bar'])
    expect(imports[0].importPath).toBe('./utils')
    expect(imports[0].isLocal).toBe(true)
    expect(imports[0].isReExport).toBe(false)
    expect(imports[0].isNamespace).toBe(false)
  })

  it('parses ES6 named imports with aliases', () => {
    const content = `import { foo as myFoo, bar as myBar } from './utils'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports[0].importedNames).toEqual(['foo', 'bar'])
  })

  it('parses ES6 default imports', () => {
    const content = `import express from 'express'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['default'])
    expect(imports[0].isLocal).toBe(false)
  })

  it('parses ES6 namespace imports', () => {
    const content = `import * as path from 'path'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['path'])
    expect(imports[0].isNamespace).toBe(true)
    expect(imports[0].isLocal).toBe(false)
  })

  it('parses CJS destructured require', () => {
    const content = `const { query, execute } = require('./db')`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['query', 'execute'])
    expect(imports[0].isLocal).toBe(true)
  })

  it('parses CJS default require', () => {
    const content = `const db = require('./db')`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['default'])
    expect(imports[0].isLocal).toBe(true)
  })

  it('parses re-exports', () => {
    const content = `export { query, getUser } from './queries'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['query', 'getUser'])
    expect(imports[0].isReExport).toBe(true)
    expect(imports[0].isLocal).toBe(true)
  })

  it('parses Python from...import', () => {
    const content = `from flask import request, jsonify`
    const imports = extractAllImports(content, 'app.py')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['request', 'jsonify'])
    expect(imports[0].importPath).toBe('flask')
    expect(imports[0].isLocal).toBe(false)
  })

  it('parses Python relative imports', () => {
    const content = `from .models import User`
    const imports = extractAllImports(content, 'app/views.py')
    expect(imports).toHaveLength(1)
    expect(imports[0].importedNames).toEqual(['User'])
    expect(imports[0].isLocal).toBe(true)
  })

  it('parses Python import statement', () => {
    const content = `import os`
    const imports = extractAllImports(content, 'app.py')
    expect(imports).toHaveLength(1)
    expect(imports[0].importPath).toBe('os')
    expect(imports[0].isNamespace).toBe(true)
  })

  it('handles @/ alias as local', () => {
    const content = `import { db } from '@/lib/database'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports[0].isLocal).toBe(true)
  })

  it('handles ~/ alias as local', () => {
    const content = `import { db } from '~/lib/database'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports[0].isLocal).toBe(true)
  })

  it('includes line numbers', () => {
    const content = `// comment\nimport { foo } from './foo'\nimport { bar } from './bar'`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports[0].line).toBe(2)
    expect(imports[1].line).toBe(3)
  })

  it('skips comments', () => {
    const content = `// import { foo } from './foo'\n/* import { bar } from './bar' */`
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports).toHaveLength(0)
  })

  it('handles multiple import types in one file', () => {
    const content = [
      `import { foo } from './foo'`,
      `import bar from './bar'`,
      `import * as utils from './utils'`,
      `const { baz } = require('./baz')`,
      `export { qux } from './qux'`,
    ].join('\n')
    const imports = extractAllImports(content, 'src/index.ts')
    expect(imports.length).toBeGreaterThanOrEqual(5)
  })
})

// ============================================================================
// extractExports
// ============================================================================

describe('extractExports', () => {
  it('extracts named function exports', () => {
    const content = `export function getUser(id: string) { return db.query(id) }`
    const exports = extractExports(content, 'src/db.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('getUser')
    expect(exports[0].kind).toBe('function')
    expect(exports[0].isDefault).toBe(false)
  })

  it('extracts async function exports', () => {
    const content = `export async function fetchData() { }`
    const exports = extractExports(content, 'src/api.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('fetchData')
    expect(exports[0].kind).toBe('function')
  })

  it('extracts class exports', () => {
    const content = `export class UserService { }`
    const exports = extractExports(content, 'src/service.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('UserService')
    expect(exports[0].kind).toBe('class')
  })

  it('extracts const/let/var exports', () => {
    const content = `export const MAX_RETRIES = 3`
    const exports = extractExports(content, 'src/config.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('MAX_RETRIES')
    expect(exports[0].kind).toBe('variable')
  })

  it('extracts default function export', () => {
    const content = `export default function handler(req, res) { }`
    const exports = extractExports(content, 'src/handler.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('handler')
    expect(exports[0].isDefault).toBe(true)
  })

  it('extracts default class export', () => {
    const content = `export default class App { }`
    const exports = extractExports(content, 'src/app.ts')
    expect(exports).toHaveLength(1)
    expect(exports[0].isDefault).toBe(true)
  })

  it('extracts re-exports with source module', () => {
    const content = `export { query, getUser } from './queries'`
    const exports = extractExports(content, 'src/index.ts')
    expect(exports).toHaveLength(2)
    expect(exports[0].kind).toBe('reexport')
    expect(exports[0].sourceModule).toBe('./queries')
  })

  it('extracts CommonJS module.exports object', () => {
    const content = `module.exports = { getUser, createUser }`
    const exports = extractExports(content, 'src/db.js')
    expect(exports).toHaveLength(2)
    expect(exports.map(e => e.name)).toEqual(['getUser', 'createUser'])
  })

  it('extracts CommonJS module.exports.prop', () => {
    const content = `module.exports.query = function() {}`
    const exports = extractExports(content, 'src/db.js')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('query')
  })

  it('extracts CommonJS exports.prop', () => {
    const content = `exports.getUser = getUser`
    const exports = extractExports(content, 'src/db.js')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('getUser')
  })

  it('extracts Python top-level def', () => {
    const content = `def get_user(id):\n    return db.query(id)`
    const exports = extractExports(content, 'db.py')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('get_user')
    expect(exports[0].kind).toBe('function')
  })

  it('extracts Python top-level class', () => {
    const content = `class UserService:\n    pass`
    const exports = extractExports(content, 'service.py')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('UserService')
    expect(exports[0].kind).toBe('class')
  })

  it('skips Python indented defs (methods)', () => {
    const content = `class Foo:\n    def bar(self):\n        pass`
    const exports = extractExports(content, 'foo.py')
    expect(exports).toHaveLength(1)
    expect(exports[0].name).toBe('Foo')
  })

  it('extracts export { ... } list', () => {
    const content = `const a = 1\nconst b = 2\nexport { a, b }`
    const exports = extractExports(content, 'src/utils.ts')
    expect(exports.some(e => e.name === 'a')).toBe(true)
    expect(exports.some(e => e.name === 'b')).toBe(true)
  })
})

// ============================================================================
// buildFileIndex & resolveImportPath
// ============================================================================

describe('resolveImportPath', () => {
  const files: ScanFile[] = [
    makeFile('src/lib/db.ts'),
    makeFile('src/lib/auth/index.ts'),
    makeFile('src/utils/helpers.tsx'),
    makeFile('src/routes/api.js'),
    makeFile('lib/shared.mjs'),
  ]
  const fileIndex = buildFileIndex(files)

  it('resolves @/ alias', () => {
    const result = resolveImportPath('@/lib/db', 'src/routes/api.ts', fileIndex)
    expect(result).not.toBeNull()
    expect(result!.path).toBe('src/lib/db.ts')
  })

  it('resolves ~/ alias', () => {
    const result = resolveImportPath('~/lib/db', 'src/routes/api.ts', fileIndex)
    expect(result).not.toBeNull()
    expect(result!.path).toBe('src/lib/db.ts')
  })

  it('resolves relative path', () => {
    const result = resolveImportPath('../lib/db', 'src/routes/api.ts', fileIndex)
    expect(result).not.toBeNull()
    expect(result!.path).toBe('src/lib/db.ts')
  })

  it('resolves index file fallback', () => {
    const result = resolveImportPath('@/lib/auth', 'src/routes/api.ts', fileIndex)
    expect(result).not.toBeNull()
    expect(result!.path).toBe('src/lib/auth/index.ts')
  })

  it('resolves with extension guessing', () => {
    const result = resolveImportPath('./helpers', 'src/utils/other.ts', fileIndex)
    expect(result).not.toBeNull()
    expect(result!.path).toBe('src/utils/helpers.tsx')
  })

  it('returns null for node_modules', () => {
    const result = resolveImportPath('express', 'src/index.ts', fileIndex)
    expect(result).toBeNull()
  })

  it('returns null for missing files', () => {
    const result = resolveImportPath('./nonexistent', 'src/index.ts', fileIndex)
    expect(result).toBeNull()
  })
})

describe('buildFileIndex', () => {
  it('indexes files by multiple path variants', () => {
    const files = [makeFile('src/lib/db.ts')]
    const index = buildFileIndex(files)
    expect(index.get('src/lib/db.ts')).toBeDefined()
    expect(index.get('src/lib/db')).toBeDefined()
    expect(index.get('@/lib/db')).toBeDefined()
    expect(index.get('~/lib/db')).toBeDefined()
  })

  it('indexes directory/index patterns', () => {
    const files = [makeFile('src/lib/auth/index.ts')]
    const index = buildFileIndex(files)
    expect(index.get('src/lib/auth')).toBeDefined()
    expect(index.get('src/lib/auth/index.ts')).toBeDefined()
  })
})
