/**
 * Registry Clients for Package Metadata
 * Fetches package information from npm and PyPI registries
 * Used by the Hallucination Firewall (Story C) to assess dependency risk
 */

// Cache for package metadata to avoid repeated requests
const npmMetadataCache = new Map<string, NPMPackageMetadata | null>()
const pypiMetadataCache = new Map<string, PyPIPackageMetadata | null>()

// Rate limiting configuration
const RATE_LIMIT_DELAY_MS = 100

/**
 * NPM Package Metadata Interface
 */
export interface NPMPackageMetadata {
  name: string
  version: string
  description?: string
  maintainers: Array<{ name: string; email?: string }>
  time: {
    created: string
    modified: string
    [version: string]: string
  }
  repository?: {
    type: string
    url: string
  }
  homepage?: string
  license?: string
  downloads?: {
    weekly: number
  }
}

/**
 * PyPI Package Metadata Interface
 */
export interface PyPIPackageMetadata {
  name: string
  version: string
  summary?: string
  author?: string
  authorEmail?: string
  license?: string
  projectUrls?: Record<string, string>
  releaseDate?: string
  requiresPython?: string
}

/**
 * Extracted dependency information
 */
export interface ExtractedDependency {
  name: string
  version?: string
  source: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'requirements'
  line: number
}

/**
 * Fetch package metadata from npm registry
 * Returns null if package doesn't exist
 */
export async function fetchNPMMetadata(packageName: string): Promise<NPMPackageMetadata | null> {
  // Check cache first
  if (npmMetadataCache.has(packageName)) {
    return npmMetadataCache.get(packageName) || null
  }

  try {
    // Fetch package info from npm registry
    const registryResponse = await fetch(
      `https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
      {
        headers: {
          'Accept': 'application/json',
        },
      }
    )

    if (!registryResponse.ok) {
      if (registryResponse.status === 404) {
        npmMetadataCache.set(packageName, null)
        return null
      }
      // Non-404 error - don't cache, might be transient
      console.warn(`[Registry] npm registry error for ${packageName}: ${registryResponse.status}`)
      return null
    }

    const data = await registryResponse.json() as Record<string, unknown>

    // Fetch download counts separately (different API)
    let weeklyDownloads = 0
    try {
      const downloadsResponse = await fetch(
        `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`
      )
      if (downloadsResponse.ok) {
        const downloadsData = await downloadsResponse.json() as Record<string, unknown>
        weeklyDownloads = (downloadsData.downloads as number) || 0
      }
    } catch {
      // Download count is optional, don't fail
    }

    const metadata: NPMPackageMetadata = {
      name: data.name as string,
      version: ((data['dist-tags'] as Record<string, string>)?.latest) || '',
      description: data.description as string | undefined,
      maintainers: (data.maintainers as Array<{ name: string; email?: string }>) || [],
      time: (data.time as NPMPackageMetadata['time']) || { created: '', modified: '' },
      repository: data.repository as NPMPackageMetadata['repository'],
      homepage: data.homepage as string | undefined,
      license: data.license as string | undefined,
      downloads: { weekly: weeklyDownloads },
    }

    npmMetadataCache.set(packageName, metadata)
    return metadata
  } catch (error) {
    console.warn(`[Registry] Failed to fetch npm metadata for ${packageName}:`, error)
    // Don't cache network errors
    return null
  }
}

/**
 * Fetch package metadata from PyPI registry
 * Returns null if package doesn't exist
 */
export async function fetchPyPIMetadata(packageName: string): Promise<PyPIPackageMetadata | null> {
  // Check cache first
  if (pypiMetadataCache.has(packageName)) {
    return pypiMetadataCache.get(packageName) || null
  }

  try {
    const response = await fetch(
      `https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`,
      {
        headers: {
          'Accept': 'application/json',
        },
      }
    )

    if (!response.ok) {
      if (response.status === 404) {
        pypiMetadataCache.set(packageName, null)
        return null
      }
      console.warn(`[Registry] PyPI registry error for ${packageName}: ${response.status}`)
      return null
    }

    const data = await response.json() as Record<string, unknown>
    const info = (data.info || {}) as Record<string, unknown>

    // Get release date from the latest version
    let releaseDate: string | undefined
    const releases = (data.releases as Record<string, Array<{ upload_time?: string }>>)?.[info.version as string]
    if (releases && releases.length > 0) {
      releaseDate = releases[0].upload_time
    }

    const metadata: PyPIPackageMetadata = {
      name: info.name as string,
      version: info.version as string,
      summary: info.summary as string | undefined,
      author: info.author as string | undefined,
      authorEmail: info.author_email as string | undefined,
      license: info.license as string | undefined,
      projectUrls: info.project_urls as Record<string, string> | undefined,
      releaseDate,
      requiresPython: info.requires_python as string | undefined,
    }

    pypiMetadataCache.set(packageName, metadata)
    return metadata
  } catch (error) {
    console.warn(`[Registry] Failed to fetch PyPI metadata for ${packageName}:`, error)
    return null
  }
}

/**
 * Extract dependencies from package.json content
 */
export function extractNpmDependencies(content: string): ExtractedDependency[] {
  try {
    const pkg = JSON.parse(content)
    const deps: ExtractedDependency[] = []
    const lines = content.split('\n')

    const depSections = [
      { key: 'dependencies', source: 'dependencies' as const },
      { key: 'devDependencies', source: 'devDependencies' as const },
      { key: 'peerDependencies', source: 'peerDependencies' as const },
      { key: 'optionalDependencies', source: 'optionalDependencies' as const },
    ]

    for (const { key, source } of depSections) {
      const depsObj = pkg[key]
      if (!depsObj || typeof depsObj !== 'object') continue

      for (const [name, version] of Object.entries(depsObj)) {
        // Find the line number for this dependency
        const lineIndex = lines.findIndex(l => l.includes(`"${name}"`))
        deps.push({
          name,
          version: version as string,
          source,
          line: lineIndex >= 0 ? lineIndex + 1 : 1,
        })
      }
    }

    return deps
  } catch {
    return []
  }
}

/**
 * Extract dependencies from requirements.txt content
 */
export function extractPythonRequirements(content: string): ExtractedDependency[] {
  const deps: ExtractedDependency[] = []
  const lines = content.split('\n')

  lines.forEach((line, index) => {
    // Skip comments and empty lines
    const trimmed = line.trim()
    if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) {
      return
    }

    // Parse package name and optional version
    // Formats: package, package==1.0.0, package>=1.0.0, package[extra], etc.
    const match = trimmed.match(/^([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:[=<>~!]+(.+))?/)
    if (match) {
      deps.push({
        name: match[1],
        version: match[2],
        source: 'requirements',
        line: index + 1,
      })
    }
  })

  return deps
}

/**
 * Extract dependencies from pyproject.toml content
 */
export function extractPyprojectDependencies(content: string): ExtractedDependency[] {
  const deps: ExtractedDependency[] = []
  const lines = content.split('\n')

  let inDependencies = false

  lines.forEach((line, index) => {
    const trimmed = line.trim()

    // Check for dependencies section
    if (trimmed === '[project.dependencies]' || trimmed === 'dependencies = [') {
      inDependencies = true
      return
    }

    // Exit dependencies section
    if (inDependencies && (trimmed.startsWith('[') || trimmed === ']')) {
      inDependencies = false
      return
    }

    if (inDependencies) {
      // Parse dependency line: "package>=1.0.0", or package = ">=1.0.0"
      const match = trimmed.match(/^["']?([a-zA-Z0-9_-]+)(?:\[.*?\])?(?:[=<>~!]+)?/)
      if (match && match[1]) {
        deps.push({
          name: match[1],
          version: undefined,
          source: 'requirements',
          line: index + 1,
        })
      }
    }
  })

  return deps
}

/**
 * Determine the package file type from path
 */
export function getPackageFileType(filePath: string): 'npm' | 'python' | null {
  const fileName = filePath.split('/').pop()?.toLowerCase() || ''

  if (fileName === 'package.json' ||
      fileName === 'package-lock.json' ||
      fileName === 'yarn.lock' ||
      fileName === 'pnpm-lock.yaml') {
    return 'npm'
  }

  if (fileName === 'requirements.txt' ||
      fileName === 'pyproject.toml' ||
      fileName === 'pipfile' ||
      fileName === 'pipfile.lock') {
    return 'python'
  }

  return null
}

/**
 * Calculate package age in days from creation date
 */
export function calculatePackageAgeDays(createdDate: string | undefined): number {
  if (!createdDate) return Infinity // Unknown age, treat as old (safe)

  try {
    const created = new Date(createdDate)
    const now = new Date()
    const diffMs = now.getTime() - created.getTime()
    return Math.floor(diffMs / (1000 * 60 * 60 * 24))
  } catch {
    return Infinity
  }
}

/**
 * Rate limiter helper - adds delay between registry requests
 */
export async function rateLimitDelay(): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, RATE_LIMIT_DELAY_MS))
}

/**
 * Clear all caches (useful for testing)
 */
export function clearRegistryCaches(): void {
  npmMetadataCache.clear()
  pypiMetadataCache.clear()
}
