/**
 * Spec Generator
 *
 * Generates a verifiable specification from user intent.
 * The spec is the contract between what the user wants and what gets built.
 *
 * Flow: Intent -> (analyze codebase + intent) -> Spec
 */

import { readFile, writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import type {
  FlowId,
  FlowSpec,
  FlowType,
  RiskLevel,
  AcceptanceCriterion,
  SpecId,
} from './types.js'
import { createSpecId, FLOW_DEFAULTS } from './types.js'

/** Context gathered about the project for spec generation */
interface ProjectContext {
  /** Detected language/framework */
  language: string
  /** Package manager */
  packageManager: string
  /** Test framework */
  testFramework: string
  /** Key directories */
  directories: string[]
  /** Existing patterns detected */
  patterns: string[]
}

/**
 * Generate a spec from user intent.
 *
 * This creates a structured specification that the user can review and approve
 * before any code is written. In production, the spec generation would use
 * a subagent to analyze the codebase. This implementation provides the structure
 * and file persistence.
 */
export async function generateSpec(
  flowId: FlowId,
  intent: string,
  projectContext?: ProjectContext,
): Promise<FlowSpec> {
  const specId = createSpecId(flowId)
  const now = Date.now()

  // Classify the intent type
  const type = classifyIntent(intent)

  // Generate spec content
  const spec: FlowSpec = {
    id: specId,
    flowId,
    intent,
    type,
    scope: {
      estimatedFiles: estimateScope(intent),
      modules: [],
    },
    requirements: extractRequirements(intent),
    acceptanceCriteria: generateAcceptanceCriteria(intent, type),
    edgeCases: identifyEdgeCases(intent, type),
    affectedFiles: [],
    dependencies: [],
    risk: assessRisk(intent, type),
    constraints: [],
    createdAt: now,
  }

  // Persist the spec
  await persistSpec(spec)

  logForDebugging(`Spec generated: ${specId} for flow ${flowId}`)
  return spec
}

/**
 * Save spec to disk.
 */
export async function persistSpec(spec: FlowSpec): Promise<void> {
  const specsDir = join(getCwd(), FLOW_DEFAULTS.specsDir)
  await mkdir(specsDir, { recursive: true })

  const filename = `${spec.flowId}.md`
  const filepath = join(specsDir, filename)

  const content = formatSpecAsMarkdown(spec)
  await writeFile(filepath, content, 'utf-8')
}

/**
 * Load a spec from disk.
 */
export async function loadSpec(flowId: FlowId): Promise<FlowSpec | null> {
  try {
    const specsDir = join(getCwd(), FLOW_DEFAULTS.specsDir)
    const filepath = join(specsDir, `${flowId}.md`)
    const content = await readFile(filepath, 'utf-8')
    return parseSpecFromMarkdown(content, flowId)
  } catch {
    return null
  }
}

/**
 * Mark a spec as approved.
 */
export async function approveSpec(spec: FlowSpec): Promise<FlowSpec> {
  const approved = {
    ...spec,
    approvedAt: Date.now(),
  }
  await persistSpec(approved)
  return approved
}

/**
 * Format a spec as human-readable markdown.
 */
function formatSpecAsMarkdown(spec: FlowSpec): string {
  const lines: string[] = []

  lines.push(`# Flow Spec: ${spec.intent}`)
  lines.push('')
  lines.push(`**ID:** ${spec.id}`)
  lines.push(`**Type:** ${spec.type}`)
  lines.push(`**Risk:** ${spec.risk}`)
  lines.push(`**Status:** ${spec.approvedAt ? 'Approved' : 'Pending approval'}`)
  lines.push('')

  lines.push('## Requirements')
  for (const req of spec.requirements) {
    lines.push(`- ${req}`)
  }
  lines.push('')

  lines.push('## Acceptance Criteria')
  for (const criterion of spec.acceptanceCriteria) {
    const status = criterion.met ? '[x]' : '[ ]'
    lines.push(`- ${status} ${criterion.description}`)
    lines.push(`  _Verify by: ${criterion.verificationMethod}_`)
  }
  lines.push('')

  if (spec.edgeCases.length > 0) {
    lines.push('## Edge Cases')
    for (const edge of spec.edgeCases) {
      lines.push(`- ${edge}`)
    }
    lines.push('')
  }

  if (spec.constraints.length > 0) {
    lines.push('## Constraints')
    for (const constraint of spec.constraints) {
      lines.push(`- ${constraint}`)
    }
    lines.push('')
  }

  if (spec.affectedFiles.length > 0) {
    lines.push('## Affected Files')
    for (const file of spec.affectedFiles) {
      lines.push(`- \`${file}\``)
    }
    lines.push('')
  }

  // Machine-readable JSON block for parsing
  lines.push('<!-- SPEC_DATA')
  lines.push(JSON.stringify(spec, null, 2))
  lines.push('SPEC_DATA -->')

  return lines.join('\n')
}

/**
 * Parse a spec from markdown format.
 */
function parseSpecFromMarkdown(content: string, flowId: FlowId): FlowSpec {
  const dataMatch = content.match(/<!-- SPEC_DATA\n([\s\S]*?)\nSPEC_DATA -->/)
  if (dataMatch?.[1]) {
    try {
      return JSON.parse(dataMatch[1]) as FlowSpec
    } catch {
      logForDebugging('Failed to parse spec JSON from markdown')
    }
  }

  // Fallback: create a minimal spec from the markdown content
  return {
    id: createSpecId(flowId),
    flowId,
    intent: 'Unknown (parsing failed)',
    type: 'unknown',
    scope: { estimatedFiles: 0, modules: [] },
    requirements: [],
    acceptanceCriteria: [],
    edgeCases: [],
    affectedFiles: [],
    dependencies: [],
    risk: 'medium',
    constraints: [],
    createdAt: Date.now(),
  }
}

/**
 * Classify the intent into a flow type.
 */
function classifyIntent(intent: string): FlowType {
  const lower = intent.toLowerCase()

  if (/\b(fix|bug|broken|error|crash|issue)\b/.test(lower)) return 'bugfix'
  if (/\b(refactor|restructure|reorganize|clean up|cleanup)\b/.test(lower)) return 'refactor'
  if (/\b(optimize|performance|speed|fast|slow)\b/.test(lower)) return 'optimization'
  if (/\b(test|spec|coverage)\b/.test(lower)) return 'test'
  if (/\b(add|create|build|implement|feature|support)\b/.test(lower)) return 'feature'

  return 'unknown'
}

/**
 * Estimate the number of files affected by the intent.
 */
function estimateScope(intent: string): number {
  const lower = intent.toLowerCase()

  // Simple heuristics — in production, a subagent would analyze the codebase
  if (/\b(add|create)\b.*\b(endpoint|route|api)\b/.test(lower)) return 3
  if (/\b(add|create|implement)\b.*\b(auth|authentication)\b/.test(lower)) return 8
  if (/\b(add|create)\b.*\b(test|spec)\b/.test(lower)) return 2
  if (/\b(fix|bug)\b/.test(lower)) return 2
  if (/\b(refactor)\b/.test(lower)) return 5
  if (/\b(feature|module)\b/.test(lower)) return 6

  return 4 // default estimate
}

/**
 * Extract high-level requirements from intent.
 */
function extractRequirements(intent: string): string[] {
  const requirements: string[] = []

  // Parse intent into discrete requirements
  const sentences = intent.split(/[.!]+/).filter(s => s.trim().length > 0)
  for (const sentence of sentences) {
    const trimmed = sentence.trim()
    if (trimmed.length > 0) {
      requirements.push(trimmed)
    }
  }

  // If no clear requirements extracted, use the full intent
  if (requirements.length === 0) {
    requirements.push(intent)
  }

  return requirements
}

/**
 * Generate acceptance criteria from intent and type.
 */
function generateAcceptanceCriteria(
  intent: string,
  type: FlowType,
): AcceptanceCriterion[] {
  const criteria: AcceptanceCriterion[] = []
  const lower = intent.toLowerCase()

  // Universal: code compiles/validates
  criteria.push({
    description: 'Code compiles/type-checks without errors',
    verificationMethod: 'Run build or type-check command',
    met: false,
  })

  // Universal: existing tests still pass
  criteria.push({
    description: 'All existing tests continue to pass',
    verificationMethod: 'Run full test suite',
    met: false,
  })

  // Type-specific criteria
  if (type === 'feature') {
    criteria.push({
      description: 'New functionality works as described in the intent',
      verificationMethod: 'Write and pass new test(s) covering the feature',
      met: false,
    })

    if (/\b(api|endpoint|route)\b/.test(lower)) {
      criteria.push({
        description: 'New endpoint returns correct response format',
        verificationMethod: 'Test with sample request',
        met: false,
      })
    }

    if (/\b(auth|login|token)\b/.test(lower)) {
      criteria.push({
        description: 'Authentication flow handles invalid credentials',
        verificationMethod: 'Test with invalid credentials',
        met: false,
      })
    }
  }

  if (type === 'bugfix') {
    criteria.push({
      description: 'The reported issue no longer occurs',
      verificationMethod: 'Reproduce the original issue and verify it is fixed',
      met: false,
    })

    criteria.push({
      description: 'Fix includes a regression test',
      verificationMethod: 'Check that a new test was added that would have caught the bug',
      met: false,
    })
  }

  if (type === 'refactor') {
    criteria.push({
      description: 'Behavior is unchanged after refactoring',
      verificationMethod: 'All existing tests pass without modification',
      met: false,
    })
  }

  return criteria
}

/**
 * Identify edge cases from intent and type.
 */
function identifyEdgeCases(intent: string, type: FlowType): string[] {
  const edges: string[] = []
  const lower = intent.toLowerCase()

  if (/\b(auth|token|session)\b/.test(lower)) {
    edges.push('Expired or invalid token handling')
    edges.push('Concurrent token refresh race conditions')
  }

  if (/\b(api|endpoint|route)\b/.test(lower)) {
    edges.push('Invalid input validation')
    edges.push('Rate limiting behavior')
  }

  if (/\b(database|db|query|migration)\b/.test(lower)) {
    edges.push('Empty result set handling')
    edges.push('Data integrity constraints')
  }

  if (/\b(async|concurrent|parallel)\b/.test(lower)) {
    edges.push('Error in concurrent operations')
    edges.push('Timeout handling')
  }

  if (type === 'bugfix') {
    edges.push('Related code paths that might have the same issue')
  }

  return edges
}

/**
 * Assess risk level based on intent and type.
 */
function assessRisk(intent: string, type: FlowType): RiskLevel {
  const lower = intent.toLowerCase()

  // High risk indicators
  if (/\b(auth|security|password|token|payment)\b/.test(lower)) return 'high'
  if (/\b(database|migration|schema)\b/.test(lower)) return 'high'
  if (/\b(production|deploy|release)\b/.test(lower)) return 'high'

  // Medium risk indicators
  if (type === 'refactor') return 'medium'
  if (/\b(api|endpoint|route)\b/.test(lower)) return 'medium'
  if (/\b(config|settings|environment)\b/.test(lower)) return 'medium'

  // Default
  return 'low'
}
