/**
 * Suppression Config Loader
 * Loads and validates suppression configuration from various file formats
 */

import { readFileSync, writeFileSync, existsSync } from 'fs'
import { join, dirname } from 'path'
import * as yaml from 'js-yaml'
import type {
  SuppressionConfig,
  RuleSuppression,
  FindingSuppression,
} from './types'
import {
  SUPPRESSION_CONFIG_FILES,
  DEFAULT_SUPPRESSION_CONFIG,
} from './types'
import type { VulnerabilityCategory } from '../../shared/types'

/**
 * Result of loading suppression config
 */
export interface ConfigLoadResult {
  /** The loaded config (or default if none found) */
  config: SuppressionConfig
  /** Path to the config file (if found) */
  configPath?: string
  /** Whether a config file was found */
  found: boolean
  /** Any errors encountered during loading */
  errors: string[]
}

/**
 * Find the config file by walking up the directory tree
 */
export function findConfigFile(startPath: string): string | null {
  let currentDir = startPath

  // Walk up directory tree looking for config file
  while (currentDir !== dirname(currentDir)) {
    for (const filename of SUPPRESSION_CONFIG_FILES) {
      const configPath = join(currentDir, filename)
      if (existsSync(configPath)) {
        return configPath
      }
    }
    currentDir = dirname(currentDir)
  }

  // Check root directory one more time
  for (const filename of SUPPRESSION_CONFIG_FILES) {
    const configPath = join(currentDir, filename)
    if (existsSync(configPath)) {
      return configPath
    }
  }

  return null
}

/**
 * Parse config file content based on extension
 */
function parseConfigContent(content: string, filePath: string): unknown {
  const isYaml = filePath.endsWith('.yaml') || filePath.endsWith('.yml')

  if (isYaml) {
    return yaml.load(content)
  }

  // JSON or .oculumrc (treat as JSON)
  return JSON.parse(content)
}

/**
 * Validate suppression config structure
 */
function validateConfig(data: unknown): { valid: boolean; errors: string[] } {
  const errors: string[] = []

  if (!data || typeof data !== 'object') {
    return { valid: false, errors: ['Config must be an object'] }
  }

  const config = data as Record<string, unknown>

  // Validate version
  if (config.version !== undefined && config.version !== 1) {
    errors.push(`Unsupported config version: ${config.version}. Only version 1 is supported.`)
  }

  // Validate suppressions
  if (config.suppressions !== undefined) {
    if (typeof config.suppressions !== 'object' || config.suppressions === null) {
      errors.push('suppressions must be an object')
    } else {
      const suppressions = config.suppressions as Record<string, unknown>

      // Validate rules
      if (suppressions.rules !== undefined) {
        if (!Array.isArray(suppressions.rules)) {
          errors.push('suppressions.rules must be an array')
        } else {
          suppressions.rules.forEach((rule, i) => {
            if (!rule || typeof rule !== 'object') {
              errors.push(`suppressions.rules[${i}] must be an object`)
            } else {
              const r = rule as Record<string, unknown>
              if (!r.category || typeof r.category !== 'string') {
                errors.push(`suppressions.rules[${i}].category is required`)
              }
              if (!r.reason || typeof r.reason !== 'string') {
                errors.push(`suppressions.rules[${i}].reason is required`)
              }
              if (r.expires && typeof r.expires !== 'string') {
                errors.push(`suppressions.rules[${i}].expires must be a string (ISO date)`)
              }
              if (r.paths && !Array.isArray(r.paths)) {
                errors.push(`suppressions.rules[${i}].paths must be an array`)
              }
            }
          })
        }
      }

      // Validate findings
      if (suppressions.findings !== undefined) {
        if (!Array.isArray(suppressions.findings)) {
          errors.push('suppressions.findings must be an array')
        } else {
          suppressions.findings.forEach((finding, i) => {
            if (!finding || typeof finding !== 'object') {
              errors.push(`suppressions.findings[${i}] must be an object`)
            } else {
              const f = finding as Record<string, unknown>
              if (!f.hash || typeof f.hash !== 'string') {
                errors.push(`suppressions.findings[${i}].hash is required`)
              }
              if (!f.file || typeof f.file !== 'string') {
                errors.push(`suppressions.findings[${i}].file is required`)
              }
              if (!f.reason || typeof f.reason !== 'string') {
                errors.push(`suppressions.findings[${i}].reason is required`)
              }
              if (f.expires && typeof f.expires !== 'string') {
                errors.push(`suppressions.findings[${i}].expires must be a string (ISO date)`)
              }
            }
          })
        }
      }
    }
  }

  // Validate ignore patterns
  if (config.ignore !== undefined) {
    if (!Array.isArray(config.ignore)) {
      errors.push('ignore must be an array of strings')
    } else {
      config.ignore.forEach((pattern, i) => {
        if (typeof pattern !== 'string') {
          errors.push(`ignore[${i}] must be a string`)
        }
      })
    }
  }

  return { valid: errors.length === 0, errors }
}

/**
 * Load suppression config from a specific path
 */
export function loadConfigFromPath(configPath: string): ConfigLoadResult {
  const errors: string[] = []

  try {
    const content = readFileSync(configPath, 'utf-8')
    const data = parseConfigContent(content, configPath)

    const validation = validateConfig(data)
    if (!validation.valid) {
      return {
        config: DEFAULT_SUPPRESSION_CONFIG,
        configPath,
        found: true,
        errors: validation.errors,
      }
    }

    // Cast to config type (after validation)
    const config = data as SuppressionConfig

    // Ensure defaults
    return {
      config: {
        version: config.version || 1,
        suppressions: {
          rules: config.suppressions?.rules || [],
          findings: config.suppressions?.findings || [],
        },
        ignore: config.ignore || [],
      },
      configPath,
      found: true,
      errors,
    }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    return {
      config: DEFAULT_SUPPRESSION_CONFIG,
      configPath,
      found: true,
      errors: [`Failed to parse config: ${message}`],
    }
  }
}

/**
 * Load suppression config from a project directory
 * Searches up the directory tree for a config file
 */
export function loadSuppressionConfig(projectPath: string): ConfigLoadResult {
  const configPath = findConfigFile(projectPath)

  if (!configPath) {
    return {
      config: DEFAULT_SUPPRESSION_CONFIG,
      found: false,
      errors: [],
    }
  }

  return loadConfigFromPath(configPath)
}

/**
 * Write suppression config to a file
 */
export function writeSuppressionConfig(
  configPath: string,
  config: SuppressionConfig
): void {
  const isYaml = configPath.endsWith('.yaml') || configPath.endsWith('.yml')

  let content: string
  if (isYaml) {
    content = yaml.dump(config, {
      indent: 2,
      lineWidth: 100,
      noRefs: true,
      sortKeys: false,
    })
  } else {
    content = JSON.stringify(config, null, 2) + '\n'
  }

  writeFileSync(configPath, content, 'utf-8')
}

/**
 * Add a finding suppression to the config file
 */
export function addFindingSuppression(
  projectPath: string,
  suppression: FindingSuppression
): { success: boolean; configPath: string; error?: string } {
  // Find or create config file
  let configPath = findConfigFile(projectPath)
  let config: SuppressionConfig

  if (configPath) {
    const result = loadConfigFromPath(configPath)
    if (result.errors.length > 0) {
      return {
        success: false,
        configPath: configPath,
        error: result.errors.join(', '),
      }
    }
    config = result.config
  } else {
    // Create new config file in project root
    configPath = join(projectPath, '.oculum.yaml')
    config = { ...DEFAULT_SUPPRESSION_CONFIG }
  }

  // Initialize suppressions if needed
  if (!config.suppressions) {
    config.suppressions = {}
  }
  if (!config.suppressions.findings) {
    config.suppressions.findings = []
  }

  // Check for duplicate
  const existingIndex = config.suppressions.findings.findIndex(
    f => f.hash === suppression.hash
  )

  if (existingIndex >= 0) {
    // Update existing suppression
    config.suppressions.findings[existingIndex] = suppression
  } else {
    // Add new suppression
    config.suppressions.findings.push(suppression)
  }

  // Write config
  try {
    writeSuppressionConfig(configPath, config)
    return { success: true, configPath }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    return { success: false, configPath, error: message }
  }
}

/**
 * Remove a finding suppression from the config file
 */
export function removeFindingSuppression(
  projectPath: string,
  hash: string
): { success: boolean; configPath?: string; error?: string; removed: boolean } {
  const configPath = findConfigFile(projectPath)

  if (!configPath) {
    return { success: true, removed: false }
  }

  const result = loadConfigFromPath(configPath)
  if (result.errors.length > 0) {
    return {
      success: false,
      configPath,
      error: result.errors.join(', '),
      removed: false,
    }
  }

  const config = result.config

  if (!config.suppressions?.findings) {
    return { success: true, configPath, removed: false }
  }

  const initialLength = config.suppressions.findings.length
  config.suppressions.findings = config.suppressions.findings.filter(
    f => f.hash !== hash
  )

  const removed = config.suppressions.findings.length < initialLength

  if (removed) {
    try {
      writeSuppressionConfig(configPath, config)
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err)
      return { success: false, configPath, error: message, removed: false }
    }
  }

  return { success: true, configPath, removed }
}

/**
 * Add a rule suppression to the config file
 */
export function addRuleSuppression(
  projectPath: string,
  suppression: RuleSuppression
): { success: boolean; configPath: string; error?: string } {
  // Find or create config file
  let configPath = findConfigFile(projectPath)
  let config: SuppressionConfig

  if (configPath) {
    const result = loadConfigFromPath(configPath)
    if (result.errors.length > 0) {
      return {
        success: false,
        configPath: configPath,
        error: result.errors.join(', '),
      }
    }
    config = result.config
  } else {
    // Create new config file in project root
    configPath = join(projectPath, '.oculum.yaml')
    config = { ...DEFAULT_SUPPRESSION_CONFIG }
  }

  // Initialize suppressions if needed
  if (!config.suppressions) {
    config.suppressions = {}
  }
  if (!config.suppressions.rules) {
    config.suppressions.rules = []
  }

  // Check for duplicate (same category and paths)
  const existingIndex = config.suppressions.rules.findIndex(
    r => r.category === suppression.category &&
      JSON.stringify(r.paths || []) === JSON.stringify(suppression.paths || [])
  )

  if (existingIndex >= 0) {
    // Update existing suppression
    config.suppressions.rules[existingIndex] = suppression
  } else {
    // Add new suppression
    config.suppressions.rules.push(suppression)
  }

  // Write config
  try {
    writeSuppressionConfig(configPath, config)
    return { success: true, configPath }
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err)
    return { success: false, configPath, error: message }
  }
}

/**
 * List all suppressions in the config
 */
export function listSuppressions(projectPath: string): {
  rules: RuleSuppression[]
  findings: FindingSuppression[]
  configPath?: string
  errors: string[]
} {
  const result = loadSuppressionConfig(projectPath)

  return {
    rules: result.config.suppressions?.rules || [],
    findings: result.config.suppressions?.findings || [],
    configPath: result.configPath,
    errors: result.errors,
  }
}

/**
 * Check if a date string is expired
 */
export function isExpired(dateString: string | undefined): boolean {
  if (!dateString) {
    return false
  }

  try {
    const expirationDate = new Date(dateString)
    const now = new Date()
    return expirationDate < now
  } catch {
    // Invalid date format - treat as not expired
    return false
  }
}
