/**
 * Template Engine
 * Parses PTF (Phoenix Template Format) and renders to Terraform/Helm/Ansible
 */

import { logger } from '../lib/logger.js'
import { Template, TemplateType } from './template.js'

export interface PTFParameter {
  name: string
  type: 'string' | 'number' | 'boolean' | 'object' | 'array'
  description?: string
  default?: any
  required?: boolean
  validation?: {
    min?: number
    max?: number
    pattern?: string
    enum?: any[]
  }
}

export interface PTFOutput {
  name: string
  description?: string
  value: string
}

export interface PTFResource {
  type: string
  name: string
  properties: Record<string, any>
  dependsOn?: string[]
}

export interface PTFDocument {
  version: string
  name: string
  description?: string
  parameters?: PTFParameter[]
  resources: PTFResource[]
  outputs?: PTFOutput[]
  metadata?: Record<string, any>
}

export interface RenderOptions {
  parameters?: Record<string, any>
  region?: string
  environment?: string
  tags?: Record<string, string>
}

class TemplateEngine {
  /**
   * Parse PTF content
   */
  parsePTF(content: string): PTFDocument {
    try {
      const parsed = JSON.parse(content)

      // Validate structure
      if (!parsed.version || !parsed.name || !parsed.resources) {
        throw new Error('Invalid PTF: missing required fields (version, name, resources)')
      }

      if (!Array.isArray(parsed.resources)) {
        throw new Error('Invalid PTF: resources must be an array')
      }

      return parsed as PTFDocument
    } catch (error) {
      if (error instanceof SyntaxError) {
        throw new Error(`Invalid PTF JSON: ${error.message}`)
      }
      throw error
    }
  }

  /**
   * Validate PTF parameters
   */
  validateParameters(ptf: PTFDocument, provided: Record<string, any>): {
    valid: boolean
    errors: string[]
  } {
    const errors: string[] = []

    if (!ptf.parameters) {
      return { valid: true, errors: [] }
    }

    for (const param of ptf.parameters) {
      const value = provided[param.name]

      // Check required
      if (param.required && (value === undefined || value === null)) {
        errors.push(`Parameter "${param.name}" is required`)
        continue
      }

      // Use default if not provided
      if (value === undefined && param.default !== undefined) {
        provided[param.name] = param.default
        continue
      }

      // Skip validation if not provided and not required
      if (value === undefined) {
        continue
      }

      // Type validation
      if (!this.validateParameterType(param.type, value)) {
        errors.push(
          `Parameter "${param.name}" must be of type ${param.type}, got ${typeof value}`
        )
        continue
      }

      // Custom validation
      if (param.validation) {
        if (param.type === 'number') {
          if (param.validation.min !== undefined && value < param.validation.min) {
            errors.push(`Parameter "${param.name}" must be >= ${param.validation.min}`)
          }
          if (param.validation.max !== undefined && value > param.validation.max) {
            errors.push(`Parameter "${param.name}" must be <= ${param.validation.max}`)
          }
        }
        if (param.validation.pattern) {
          const regex = new RegExp(param.validation.pattern)
          if (!regex.test(String(value))) {
            errors.push(`Parameter "${param.name}" does not match pattern ${param.validation.pattern}`)
          }
        }
        if (param.validation.enum && !param.validation.enum.includes(value)) {
          errors.push(
            `Parameter "${param.name}" must be one of: ${param.validation.enum.join(', ')}`
          )
        }
      }
    }

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

  /**
   * Render PTF to Terraform
   */
  renderToTerraform(
    template: Template,
    options: RenderOptions = {}
  ): string {
    if (template.templateType !== TemplateType.PTF) {
      throw new Error('Template must be PTF type to render to Terraform')
    }

    const ptf = this.parsePTF(template.content)

    // Validate parameters
    const validation = this.validateParameters(ptf, options.parameters || {})
    if (!validation.valid) {
      throw new Error(`Parameter validation failed: ${validation.errors.join(', ')}`)
    }

    const parameters = { ...options.parameters } || {}

    // Generate Terraform HCL
    const lines: string[] = []

    // Terraform block
    lines.push('terraform {')
    lines.push('  required_version = ">= 1.0"')
    if (options.region) {
      lines.push('  required_providers {')
      lines.push('    phoenix = {')
      lines.push('      source = "phoenix/phoenix"')
      lines.push('      version = "~> 1.0"')
      lines.push('    }')
      lines.push('  }')
    }
    lines.push('}')

    // Variables
    if (ptf.parameters && ptf.parameters.length > 0) {
      lines.push('')
      for (const param of ptf.parameters) {
        lines.push(`variable "${param.name}" {`)
        lines.push(`  type = ${this.mapPTFTypeToTerraformType(param.type)}`)
        if (param.description) {
          lines.push(`  description = "${this.escapeString(param.description)}"`)
        }
        if (param.default !== undefined) {
          lines.push(`  default = ${this.formatTerraformValue(param.default)}`)
        }
        lines.push('}')
        lines.push('')
      }
    }

    // Resources
    for (const resource of ptf.resources) {
      lines.push(`resource "${resource.type}" "${resource.name}" {`)

      // Add depends_on if specified
      if (resource.dependsOn && resource.dependsOn.length > 0) {
        lines.push('  depends_on = [')
        for (const dep of resource.dependsOn) {
          lines.push(`    ${dep},`)
        }
        lines.push('  ]')
      }

      // Render properties
      for (const [key, value] of Object.entries(resource.properties)) {
        const rendered = this.renderTerraformProperty(key, value, parameters)
        lines.push(rendered)
      }

      // Add tags if provided
      if (options.tags) {
        lines.push('  tags = {')
        for (const [key, val] of Object.entries(options.tags)) {
          lines.push(`    ${key} = "${this.escapeString(val)}"`)
        }
        lines.push('  }')
      }

      lines.push('}')
      lines.push('')
    }

    // Outputs
    if (ptf.outputs && ptf.outputs.length > 0) {
      for (const output of ptf.outputs) {
        lines.push(`output "${output.name}" {`)
        if (output.description) {
          lines.push(`  description = "${this.escapeString(output.description)}"`)
        }
        lines.push(`  value = ${this.renderTerraformExpression(output.value, parameters)}`)
        lines.push('}')
        lines.push('')
      }
    }

    return lines.join('\n')
  }

  /**
   * Render PTF to Helm values
   */
  renderToHelm(template: Template, options: RenderOptions = {}): Record<string, any> {
    if (template.templateType !== TemplateType.PTF) {
      throw new Error('Template must be PTF type to render to Helm')
    }

    const ptf = this.parsePTF(template.content)

    // Validate parameters
    const validation = this.validateParameters(ptf, options.parameters || {})
    if (!validation.valid) {
      throw new Error(`Parameter validation failed: ${validation.errors.join(', ')}`)
    }

    const parameters = { ...options.parameters } || {}
    const values: Record<string, any> = {}

    // Convert resources to Helm values structure
    for (const resource of ptf.resources) {
      // Map resource to Helm values based on type
      if (resource.type.startsWith('helm.')) {
        const helmType = resource.type.replace('helm.', '')
        values[helmType] = this.interpolateValues(resource.properties, parameters)
      } else {
        values[resource.name] = this.interpolateValues(resource.properties, parameters)
      }
    }

    // Add metadata
    if (options.region) {
      values.region = options.region
    }
    if (options.environment) {
      values.environment = options.environment
    }
    if (options.tags) {
      values.tags = options.tags
    }

    return values
  }

  // Helper methods
  private validateParameterType(type: string, value: any): boolean {
    switch (type) {
      case 'string':
        return typeof value === 'string'
      case 'number':
        return typeof value === 'number'
      case 'boolean':
        return typeof value === 'boolean'
      case 'object':
        return typeof value === 'object' && !Array.isArray(value) && value !== null
      case 'array':
        return Array.isArray(value)
      default:
        return false
    }
  }

  private mapPTFTypeToTerraformType(type: string): string {
    switch (type) {
      case 'string':
        return 'string'
      case 'number':
        return 'number'
      case 'boolean':
        return 'bool'
      case 'object':
        return 'map(any)'
      case 'array':
        return 'list(any)'
      default:
        return 'any'
    }
  }

  private formatTerraformValue(value: any): string {
    if (typeof value === 'string') {
      return `"${this.escapeString(value)}"`
    }
    if (typeof value === 'number') {
      return String(value)
    }
    if (typeof value === 'boolean') {
      return value ? 'true' : 'false'
    }
    if (Array.isArray(value)) {
      return `[${value.map((v) => this.formatTerraformValue(v)).join(', ')}]`
    }
    if (typeof value === 'object' && value !== null) {
      const entries = Object.entries(value)
        .map(([k, v]) => `${k} = ${this.formatTerraformValue(v)}`)
        .join('\n    ')
      return `{\n    ${entries}\n  }`
    }
    return 'null'
  }

  private renderTerraformProperty(key: string, value: any, parameters: Record<string, any>): string {
    const interpolated = this.interpolateValues(value, parameters)

    if (typeof interpolated === 'string') {
      return `  ${key} = "${this.escapeString(interpolated)}"`
    }
    if (typeof interpolated === 'number' || typeof interpolated === 'boolean') {
      return `  ${key} = ${interpolated}`
    }
    if (Array.isArray(interpolated)) {
      const items = interpolated.map((v) => this.formatTerraformValue(v)).join(', ')
      return `  ${key} = [${items}]`
    }
    if (typeof interpolated === 'object' && interpolated !== null) {
      const entries = Object.entries(interpolated)
        .map(([k, v]) => `${k} = ${this.formatTerraformValue(v)}`)
        .join('\n    ')
      return `  ${key} = {\n    ${entries}\n  }`
    }
    return `  ${key} = null`
  }

  private renderTerraformExpression(expr: string, parameters: Record<string, any>): string {
    // Simple interpolation: ${param.name} -> var.param_name
    return expr.replace(/\$\{([^}]+)\}/g, (match, paramName) => {
      const value = parameters[paramName.trim()]
      if (value === undefined) {
        throw new Error(`Parameter "${paramName.trim()}" not found in expression: ${expr}`)
      }
      return this.formatTerraformValue(value)
    })
  }

  private interpolateValues(value: any, parameters: Record<string, any>): any {
    if (typeof value === 'string') {
      // Interpolate ${param.name} patterns
      return value.replace(/\$\{([^}]+)\}/g, (match, paramName) => {
        const paramValue = parameters[paramName.trim()]
        return paramValue !== undefined ? String(paramValue) : match
      })
    }
    if (Array.isArray(value)) {
      return value.map((v) => this.interpolateValues(v, parameters))
    }
    if (typeof value === 'object' && value !== null) {
      const result: Record<string, any> = {}
      for (const [k, v] of Object.entries(value)) {
        result[k] = this.interpolateValues(v, parameters)
      }
      return result
    }
    return value
  }

  private escapeString(str: string): string {
    return str.replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r')
  }
}

export const templateEngine = new TemplateEngine()

