/**
 * @fileoverview OrdoJS CLI - Docker configuration generator
 */

import { CLIError, ErrorType } from '../index.js';
import type { DeploymentConfig } from './adapter-interface.js';

export interface DockerConfig {
  baseImage: string;
  nodeVersion: string;
  buildStage: boolean;
  productionStage: boolean;
  multiStage: boolean;
  port: number;
  healthCheck: boolean;
  environment: Record<string, string>;
  volumes: string[];
  commands: string[];
}

export interface DockerfileOptions {
  multiStage?: boolean;
  nodeVersion?: string;
  port?: number;
  healthCheck?: boolean;
  environment?: Record<string, string>;
  volumes?: string[];
  commands?: string[];
}

/**
 * Docker configuration generator for OrdoJS applications
 */
export class DockerGenerator {
  private defaultConfig: DockerConfig = {
    baseImage: 'node:18-alpine',
    nodeVersion: '18',
    buildStage: true,
    productionStage: true,
    multiStage: true,
    port: 3000,
    healthCheck: true,
    environment: {},
    volumes: [],
    commands: []
  };

  /**
   * Generate Dockerfile content for an OrdoJS application
   */
  generateDockerfile(config: DeploymentConfig, options: DockerfileOptions = {}): string {
    const dockerConfig = this.mergeConfig(options);

    try {
      if (dockerConfig.multiStage) {
        return this.generateMultiStageDockerfile(dockerConfig);
      } else {
        return this.generateSimpleDockerfile(dockerConfig);
      }
    } catch (error) {
      throw new CLIError(
        `Failed to generate Dockerfile: ${error instanceof Error ? error.message : String(error)}`,
        ErrorType.DEPLOYMENT,
        'DOCKER-001'
      );
    }
  }

  /**
   * Generate docker-compose.yml content
   */
  generateDockerCompose(config: DeploymentConfig, options: DockerfileOptions = {}): string {
    const dockerConfig = this.mergeConfig(options);

    try {
      return this.generateDockerComposeContent(dockerConfig, config);
    } catch (error) {
      throw new CLIError(
        `Failed to generate docker-compose.yml: ${error instanceof Error ? error.message : String(error)}`,
        ErrorType.DEPLOYMENT,
        'DOCKER-002'
      );
    }
  }

  /**
   * Generate .dockerignore file content
   */
  generateDockerignore(): string {
    return `# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage
.grunt

# Bower dependency directory
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons
build/Release

# Dependency directories
jspm_packages/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test
.env.production

# parcel-bundler cache
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
public

# Storybook build outputs
.out
.storybook-out

# Temporary folders
tmp/
temp/

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# Grunt intermediate storage
.grunt

# Bower dependency directory
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# parcel-bundler cache
.cache
.parcel-cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# OrdoJS specific
.ordo-cache
build/
dist/
*.ordo.js
*.ordo.css

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Git
.git/
.gitignore

# Documentation
docs/
*.md

# Tests
test/
tests/
__tests__/
*.test.js
*.test.ts
*.spec.js
*.spec.ts

# Development
.dev/
dev/
`;
  }

  /**
   * Generate Kubernetes manifests
   */
  generateKubernetesManifests(config: DeploymentConfig, options: DockerfileOptions = {}): {
    deployment: string;
    service: string;
    ingress?: string;
    configMap?: string;
    secret?: string;
  } {
    const dockerConfig = this.mergeConfig(options);

    try {
      return {
        deployment: this.generateK8sDeployment(dockerConfig, config),
        service: this.generateK8sService(dockerConfig, config),
        ingress: this.generateK8sIngress(dockerConfig, config),
        configMap: this.generateK8sConfigMap(dockerConfig, config),
        secret: this.generateK8sSecret(dockerConfig, config)
      };
    } catch (error) {
      throw new CLIError(
        `Failed to generate Kubernetes manifests: ${error instanceof Error ? error.message : String(error)}`,
        ErrorType.DEPLOYMENT,
        'DOCKER-003'
      );
    }
  }

  private mergeConfig(options: DockerfileOptions): DockerConfig {
    return {
      ...this.defaultConfig,
      ...options,
      environment: { ...this.defaultConfig.environment, ...options.environment },
      volumes: [...this.defaultConfig.volumes, ...(options.volumes || [])],
      commands: [...this.defaultConfig.commands, ...(options.commands || [])]
    };
  }

  private generateMultiStageDockerfile(config: DockerConfig): string {
    return `# Multi-stage Dockerfile for OrdoJS application
FROM node:${config.nodeVersion}-alpine AS base

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml ./

# Install pnpm
RUN npm install -g pnpm

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build stage
FROM base AS builder

# Build the application
RUN pnpm run build

# Production stage
FROM node:${config.nodeVersion}-alpine AS production

# Create app user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S ordojs -u 1001

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml ./

# Install pnpm and production dependencies only
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile --prod

# Copy built application from builder stage
COPY --from=builder --chown=ordojs:nodejs /app/dist ./dist
COPY --from=builder --chown=ordojs:nodejs /app/public ./public

# Switch to non-root user
USER ordojs

# Expose port
EXPOSE ${config.port}

# Set environment variables
${this.generateEnvironmentVariables(config.environment)}

# Health check
${config.healthCheck ? this.generateHealthCheck(config.port) : ''}

# Start the application
CMD ["node", "dist/server.js"]
`;
  }

  private generateSimpleDockerfile(config: DockerConfig): string {
    return `# Simple Dockerfile for OrdoJS application
FROM node:${config.nodeVersion}-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml ./

# Install pnpm
RUN npm install -g pnpm

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build the application
RUN pnpm run build

# Expose port
EXPOSE ${config.port}

# Set environment variables
${this.generateEnvironmentVariables(config.environment)}

# Health check
${config.healthCheck ? this.generateHealthCheck(config.port) : ''}

# Start the application
CMD ["node", "dist/server.js"]
`;
  }

  private generateDockerComposeContent(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    return `version: '3.8'

services:
  ordojs-app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "${config.port}:${config.port}"
    environment:
${this.generateDockerComposeEnvironment(config.environment)}
    volumes:
${this.generateDockerComposeVolumes(config.volumes)}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:${config.port}/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  default:
    name: ordojs-network
`;
  }

  private generateK8sDeployment(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    return `apiVersion: apps/v1
kind: Deployment
metadata:
  name: ordojs-app
  labels:
    app: ordojs-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ordojs-app
  template:
    metadata:
      labels:
        app: ordojs-app
    spec:
      containers:
      - name: ordojs-app
        image: ordojs-app:latest
        ports:
        - containerPort: ${config.port}
        env:
${this.generateK8sEnvironment(config.environment)}
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: ${config.port}
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: ${config.port}
          initialDelaySeconds: 5
          periodSeconds: 5
`;
  }

  private generateK8sService(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    return `apiVersion: v1
kind: Service
metadata:
  name: ordojs-app-service
spec:
  selector:
    app: ordojs-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: ${config.port}
  type: ClusterIP
`;
  }

  private generateK8sIngress(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    return `apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ordojs-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: ordojs-app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ordojs-app-service
            port:
              number: 80
`;
  }

  private generateK8sConfigMap(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    const envVars = Object.entries(config.environment)
      .filter(([key]) => !key.toLowerCase().includes('secret') && !key.toLowerCase().includes('password'))
      .map(([key, value]) => `  ${key}: "${value}"`)
      .join('\n');

    return `apiVersion: v1
kind: ConfigMap
metadata:
  name: ordojs-app-config
data:
${envVars}
`;
  }

  private generateK8sSecret(config: DockerConfig, deploymentConfig: DeploymentConfig): string {
    const secretVars = Object.entries(config.environment)
      .filter(([key]) => key.toLowerCase().includes('secret') || key.toLowerCase().includes('password'))
      .map(([key, value]) => `  ${key}: ${Buffer.from(value).toString('base64')}`)
      .join('\n');

    if (!secretVars) {
      return '';
    }

    return `apiVersion: v1
kind: Secret
metadata:
  name: ordojs-app-secret
type: Opaque
data:
${secretVars}
`;
  }

  private generateEnvironmentVariables(environment: Record<string, string>): string {
    return Object.entries(environment)
      .map(([key, value]) => `ENV ${key}=${value}`)
      .join('\n');
  }

  private generateHealthCheck(port: number): string {
    return `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
  CMD curl -f http://localhost:${port}/health || exit 1`;
  }

  private generateDockerComposeEnvironment(environment: Record<string, string>): string {
    return Object.entries(environment)
      .map(([key, value]) => `      - ${key}=${value}`)
      .join('\n');
  }

  private generateDockerComposeVolumes(volumes: string[]): string {
    return volumes
      .map(volume => `      - ${volume}`)
      .join('\n');
  }

  private generateK8sEnvironment(environment: Record<string, string>): string {
    return Object.entries(environment)
      .map(([key, value]) => `        - name: ${key}
          value: "${value}"`)
      .join('\n');
  }
}
