/**
 * @fileoverview OrdoJS CLI - AWS Lambda deployment adapter
 * Adapter for deploying to AWS Lambda and API Gateway
 */

import path from 'path';
import { AssetOptimizer, OptimizationResults } from '../../asset-optimizer.js';
import { mkdir, writeFile } from '../../fs.js';
import { logger } from '../../logger.js';
import { DeploymentAdapter, DeploymentConfig, DeploymentResult } from '../adapter-interface.js';

/**
 * AWS Lambda-specific configuration
 */
export interface AWSLambdaConfig extends DeploymentConfig {
  /**
   * AWS Lambda settings
   */
  settings?: {
    /**
     * AWS region
     */
    region?: string;

    /**
     * Lambda function name
     */
    functionName?: string;

    /**
     * Lambda memory size in MB
     */
    memorySize?: number;

    /**
     * Lambda timeout in seconds
     */
    timeout?: number;

    /**
     * Lambda runtime
     */
    runtime?: string;

    /**
     * API Gateway stage name
     */
    stageName?: string;

    /**
     * Whether to use AWS CloudFront
     */
    useCloudFront?: boolean;

    /**
     * Whether to use AWS S3 for static assets
     */
    useS3ForStaticAssets?: boolean;

    /**
     * S3 bucket name for static assets
     */
    s3BucketName?: string;

    /**
     * Whether to use AWS CloudWatch for logs
     */
    useCloudWatch?: boolean;

    /**
     * Whether to use AWS X-Ray for tracing
     */
    useXRay?: boolean;
  };
}

/**
 * AWS Lambda deployment adapter
 */
export class AWSLambdaAdapter implements DeploymentAdapter {
  /**
   * Adapter name
   */
  name = 'aws-lambda';

  /**
   * Adapter description
   */
  description = 'Deploy to AWS Lambda and API Gateway';

  /**
   * Validate AWS Lambda deployment configuration
   * @param config Deployment configuration
   * @returns Validation result
   */
  validateConfig(config: DeploymentConfig): { valid: boolean; errors?: string[] } {
    const errors: string[] = [];

    // Check required fields
    if (!config.outputDir) {
      errors.push('outputDir is required');
    }

    // Validate AWS Lambda-specific settings
    const awsConfig = config as AWSLambdaConfig;
    if (awsConfig.settings) {
      // Validate region if provided
      if (awsConfig.settings.region && !/^[a-z]{2}-[a-z]+-\d$/.test(awsConfig.settings.region)) {
        errors.push('Invalid AWS region format (e.g., us-east-1)');
      }

      // Validate function name if provided
      if (awsConfig.settings.functionName && !/^[a-zA-Z0-9-_]+$/.test(awsConfig.settings.functionName)) {
        errors.push('Function name must contain only letters, numbers, hyphens, and underscores');
      }

      // Validate memory size if provided
      if (awsConfig.settings.memorySize && (awsConfig.settings.memorySize < 128 || awsConfig.settings.memorySize > 10240)) {
        errors.push('Memory size must be between 128 MB and 10240 MB');
      }

      // Validate timeout if provided
      if (awsConfig.settings.timeout && (awsConfig.settings.timeout < 1 || awsConfig.settings.timeout > 900)) {
        errors.push('Timeout must be between 1 and 900 seconds');
      }
    }

    return {
      valid: errors.length === 0,
      errors: errors.length > 0 ? errors : undefined
    };
  }

  /**
   * Prepare AWS Lambda deployment
   * @param config Deployment configuration
   * @returns Deployment result
   */
  async prepareDeployment(config: DeploymentConfig): Promise<DeploymentResult> {
    const awsConfig = config as AWSLambdaConfig;
    const generatedFiles: Array<{ path: string; content: string }> = [];

    try {
      // Create build output directory if it doesn't exist
      await mkdir(config.outputDir, { recursive: true });

      // Generate serverless.yml configuration
      const serverlessYamlContent = this.generateServerlessConfig(awsConfig);
      const serverlessYamlPath = path.join(config.outputDir, 'serverless.yml');

      await writeFile(serverlessYamlPath, serverlessYamlContent);
      generatedFiles.push({
        path: serverlessYamlPath,
        content: serverlessYamlContent
      });

      // Generate AWS SAM template
      const samTemplateContent = this.generateSAMTemplate(awsConfig);
      const samTemplatePath = path.join(config.outputDir, 'template.yaml');

      await writeFile(samTemplatePath, samTemplateContent);
      generatedFiles.push({
        path: samTemplatePath,
        content: samTemplateContent
      });

      // Generate CloudFormation template
      const cfnTemplateContent = this.generateCloudFormationTemplate(awsConfig);
      const cfnTemplatePath = path.join(config.outputDir, 'cloudformation.yaml');

      await writeFile(cfnTemplatePath, cfnTemplateContent);
      generatedFiles.push({
        path: cfnTemplatePath,
        content: cfnTemplateContent
      });

      // Generate Lambda handler
      const handlerContent = this.generateLambdaHandler(awsConfig);
      const handlerDir = path.join(config.outputDir, 'src');
      const handlerPath = path.join(handlerDir, 'handler.js');

      await mkdir(handlerDir, { recursive: true });
      await writeFile(handlerPath, handlerContent);
      generatedFiles.push({
        path: handlerPath,
        content: handlerContent
      });

      // Generate package.json for Lambda
      const packageJsonContent = this.generatePackageJson(awsConfig);
      const packageJsonPath = path.join(config.outputDir, 'package.json');

      await writeFile(packageJsonPath, packageJsonContent);
      generatedFiles.push({
        path: packageJsonPath,
        content: packageJsonContent
      });

      // Generate deployment script
      const deployScriptContent = this.generateDeployScript(awsConfig);
      const deployScriptPath = path.join(config.outputDir, 'deploy.sh');

      await writeFile(deployScriptPath, deployScriptContent);
      generatedFiles.push({
        path: deployScriptPath,
        content: deployScriptContent
      });

      // Optimize assets for AWS Lambda
      const optimizationResults = await this.optimizeForDeployment(config, config.outputDir);

      return {
        success: true,
        generatedFiles,
        instructions: this.getDeploymentInstructions(awsConfig),
        optimizationResults
      };
    } catch (error) {
      logger.error(`AWS Lambda deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`);

      return {
        success: false,
        error: `AWS Lambda deployment preparation failed: ${error instanceof Error ? error.message : String(error)}`,
        generatedFiles,
        instructions: 'An error occurred during AWS Lambda deployment preparation. Please check the logs for details.'
      };
    }
  }

  /**
   * Generate Serverless Framework configuration
   * @param config AWS Lambda configuration
   * @returns Serverless Framework configuration YAML
   */
  private generateServerlessConfig(config: AWSLambdaConfig): string {
    const functionName = config.settings?.functionName || 'ordojs-app';
    const region = config.settings?.region || 'us-east-1';
    const memorySize = config.settings?.memorySize || 1024;
    const timeout = config.settings?.timeout || 30;
    const runtime = config.settings?.runtime || 'nodejs18.x';
    const stageName = config.settings?.stageName || 'prod';

    return `# Serverless Framework configuration for OrdoJS
service: ${functionName}

provider:
  name: aws
  runtime: ${runtime}
  stage: ${stageName}
  region: ${region}
  memorySize: ${memorySize}
  timeout: ${timeout}
  environment:
${Object.entries(config.env || {}).map(([key, value]) => `    ${key}: ${value}`).join('\n')}

functions:
  app:
    handler: src/handler.handler
    events:
      - http:
          path: /
          method: ANY
          cors: true
      - http:
          path: /{proxy+}
          method: ANY
          cors: true

plugins:
  - serverless-offline
  - serverless-webpack
${config.settings?.useCloudWatch ? '  - serverless-plugin-aws-alerts\n' : ''}
${config.settings?.useXRay ? '  - serverless-plugin-tracing\n' : ''}

custom:
  webpack:
    webpackConfig: webpack.config.js
    includeModules: true
    packager: 'npm'
${config.settings?.useCloudWatch ? `
  alerts:
    stages:
      - ${stageName}
    topics:
      alarm:
        topic: \${self:service}-\${self:provider.stage}-alerts
        notifications:
          - protocol: email
            endpoint: your-email@example.com
    alarms:
      - functionErrors
      - functionThrottles
      - functionInvocations
      - functionDuration
` : ''}
`;
  }

  /**
   * Generate AWS SAM template
   * @param config AWS Lambda configuration
   * @returns AWS SAM template YAML
   */
  private generateSAMTemplate(config: AWSLambdaConfig): string {
    const functionName = config.settings?.functionName || 'ordojs-app';
    const memorySize = config.settings?.memorySize || 1024;
    const timeout = config.settings?.timeout || 30;
    const runtime = config.settings?.runtime || 'nodejs18.x';

    return `# AWS SAM template for OrdoJS
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: OrdoJS application deployed to AWS Lambda

Globals:
  Function:
    Timeout: ${timeout}
    Runtime: ${runtime}
    MemorySize: ${memorySize}
    Environment:
      Variables:
${Object.entries(config.env || {}).map(([key, value]) => `        ${key}: ${value}`).join('\n')}

Resources:
  OrdoJSFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./
      Handler: src/handler.handler
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        ProxyApiEvent:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY
${config.settings?.useXRay ? '      Tracing: Active\n' : ''}

  ApiGatewayApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: ${config.settings?.stageName || 'prod'}
      Cors:
        AllowMethods: "'*'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"

Outputs:
  OrdoJSFunction:
    Description: OrdoJS Lambda Function ARN
    Value: !GetAtt OrdoJSFunction.Arn
  ApiGatewayApi:
    Description: API Gateway endpoint URL
    Value: !Sub https://\${ApiGatewayApi}.execute-api.\${AWS::Region}.amazonaws.com/${config.settings?.stageName || 'prod'}/
  OrdoJSFunctionRole:
    Description: Implicit IAM Role created for the function
    Value: !GetAtt OrdoJSFunctionRole.Arn
`;
  }

  /**
   * Generate AWS CloudFormation template
   * @param config AWS Lambda configuration
   * @returns AWS CloudFormation template YAML
   */
  private generateCloudFormationTemplate(config: AWSLambdaConfig): string {
    const functionName = config.settings?.functionName || 'ordojs-app';
    const memorySize = config.settings?.memorySize || 1024;
    const timeout = config.settings?.timeout || 30;
    const runtime = config.settings?.runtime || 'nodejs18.x';
    const stageName = config.settings?.stageName || 'prod';

    return `# AWS CloudFormation template for OrdoJS
AWSTemplateFormatVersion: '2010-09-09'
Description: OrdoJS application deployed to AWS Lambda

Resources:
  OrdoJSLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: ${functionName}
      Handler: src/handler.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: ${runtime}
      MemorySize: ${memorySize}
      Timeout: ${timeout}
      Environment:
        Variables:
${Object.entries(config.env || {}).map(([key, value]) => `          ${key}: ${value}`).join('\n')}
${config.settings?.useXRay ? '      TracingConfig:\n        Mode: Active\n' : ''}

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
${config.settings?.useXRay ? '        - \'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess\'\n' : ''}

  ApiGateway:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: ${functionName}-api
      Description: API Gateway for OrdoJS application

  ApiGatewayRootMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref ApiGateway
      ResourceId: !GetAtt ApiGateway.RootResourceId
      HttpMethod: ANY
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${OrdoJSLambdaFunction.Arn}/invocations

  ApiGatewayProxyResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref ApiGateway
      ParentId: !GetAtt ApiGateway.RootResourceId
      PathPart: '{proxy+}'

  ApiGatewayProxyMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref ApiGateway
      ResourceId: !Ref ApiGatewayProxyResource
      HttpMethod: ANY
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${OrdoJSLambdaFunction.Arn}/invocations

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - ApiGatewayRootMethod
      - ApiGatewayProxyMethod
    Properties:
      RestApiId: !Ref ApiGateway
      StageName: ${stageName}

  LambdaApiGatewayPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OrdoJSLambdaFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${ApiGateway}/*/*/*

Outputs:
  ApiGatewayUrl:
    Description: URL of the API Gateway endpoint
    Value: !Sub https://\${ApiGateway}.execute-api.\${AWS::Region}.amazonaws.com/${stageName}/
  LambdaFunctionArn:
    Description: ARN of the Lambda function
    Value: !GetAtt OrdoJSLambdaFunction.Arn
`;
  }

  /**
   * Generate Lambda handler
   * @param config AWS Lambda configuration
   * @returns Lambda handler JavaScript code
   */
  private generateLambdaHandler(config: AWSLambdaConfig): string {
    return `/**
 * AWS Lambda handler for OrdoJS application
 */

const serverless = require('serverless-http');
const express = require('express');
const path = require('path');
const fs = require('fs');

// Create Express app
const app = express();

// Serve static files
app.use(express.static(path.join(__dirname, '../public')));

// API routes
app.get('/api/hello', (req, res) => {
  res.json({
    message: 'Hello from OrdoJS on AWS Lambda!',
    timestamp: new Date().toISOString()
  });
});

// Handle all other routes with the main HTML file
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../public/index.html'));
});

// Create serverless handler
const handler = serverless(app);

// Export the handler function
module.exports.handler = async (event, context) => {
  // Enable AWS X-Ray tracing if configured
  ${config.settings?.useXRay ? `
  const AWSXRay = require('aws-xray-sdk');
  AWSXRay.captureHTTPsGlobal(require('http'));
  AWSXRay.captureHTTPsGlobal(require('https'));
  ` : ''}

  return await handler(event, context);
};
`;
  }

  /**
   * Generate package.json for Lambda
   * @param config AWS Lambda configuration
   * @returns package.json content
   */
  private generatePackageJson(config: AWSLambdaConfig): string {
    const functionName = config.settings?.functionName || 'ordojs-app';

    return JSON.stringify({
      name: functionName,
      version: '1.0.0',
      description: 'OrdoJS application deployed to AWS Lambda',
      main: 'src/handler.js',
      scripts: {
        deploy: 'bash ./deploy.sh',
        'deploy:serverless': 'serverless deploy',
        'deploy:sam': 'sam deploy',
        'deploy:cloudformation': 'aws cloudformation deploy --template-file cloudformation.yaml --stack-name ' + functionName
      },
      dependencies: {
        express: '^4.18.2',
        'serverless-http': '^3.2.0',
        ...(config.settings?.useXRay ? { 'aws-xray-sdk': '^3.5.0' } : {})
      },
      devDependencies: {
        'serverless': '^3.34.0',
        'serverless-offline': '^12.0.4',
        'serverless-webpack': '^5.13.0',
        ...(config.settings?.useCloudWatch ? { 'serverless-plugin-aws-alerts': '^1.7.5' } : {}),
        ...(config.settings?.useXRay ? { 'serverless-plugin-tracing': '^2.0.0' } : {})
      }
    }, null, 2);
  }

  /**
   * Generate deployment script
   * @param config AWS Lambda configuration
   * @returns Deployment script content
   */
  private generateDeployScript(config: AWSLambdaConfig): string {
    return `#!/bin/bash
# Deployment script for OrdoJS on AWS Lambda

echo "Deploying OrdoJS application to AWS Lambda..."

# Check if AWS CLI is installed
if ! command -v aws &> /dev/null; then
  echo "AWS CLI is not installed. Please install it first."
  exit 1
fi

# Check if AWS is configured
if ! aws sts get-caller-identity &> /dev/null; then
  echo "AWS CLI is not configured. Please run 'aws configure' first."
  exit 1
fi

# Choose deployment method
echo "Choose deployment method:"
echo "1. Serverless Framework"
echo "2. AWS SAM"
echo "3. AWS CloudFormation"
read -p "Enter your choice (1-3): " choice

case $choice in
  1)
    echo "Deploying with Serverless Framework..."
    npm install
    npx serverless deploy
    ;;
  2)
    echo "Deploying with AWS SAM..."
    sam build
    sam deploy --guided
    ;;
  3)
    echo "Deploying with AWS CloudFormation..."
    aws cloudformation package \\
      --template-file cloudformation.yaml \\
      --s3-bucket your-deployment-bucket \\
      --output-template-file packaged.yaml

    aws cloudformation deploy \\
      --template-file packaged.yaml \\
      --stack-name ${config.settings?.functionName || 'ordojs-app'} \\
      --capabilities CAPABILITY_IAM
    ;;
  *)
    echo "Invalid choice. Exiting."
    exit 1
    ;;
esac

echo "Deployment completed!"
`;
  }

  /**
   * Get deployment instructions
   * @param config AWS Lambda configuration
   * @returns Deployment instructions
   */
  private getDeploymentInstructions(config: AWSLambdaConfig): string {
    return `
# AWS Lambda Deployment Instructions

Your project has been prepared for deployment to AWS Lambda.

## Prerequisites

1. Install the AWS CLI:
   \`\`\`
   pip install awscli
   \`\`\`

2. Configure AWS CLI:
   \`\`\`
   aws configure
   \`\`\`

3. Install required tools based on your preferred deployment method:
   - Serverless Framework: \`npm install -g serverless\`
   - AWS SAM: \`pip install aws-sam-cli\`

## Deployment Options

You have three options for deploying your application:

### 1. Using Serverless Framework

\`\`\`
cd ${config.outputDir}
npm install
npx serverless deploy
\`\`\`

### 2. Using AWS SAM

\`\`\`
cd ${config.outputDir}
sam build
sam deploy --guided
\`\`\`

### 3. Using AWS CloudFormation

\`\`\`
cd ${config.outputDir}
aws cloudformation package \\
  --template-file cloudformation.yaml \\
  --s3-bucket your-deployment-bucket \\
  --output-template-file packaged.yaml

aws cloudformation deploy \\
  --template-file packaged.yaml \\
  --stack-name ${config.settings?.functionName || 'ordojs-app'} \\
  --capabilities CAPABILITY_IAM
\`\`\`

## Quick Deployment

For convenience, a deployment script has been generated. Run:

\`\`\`
cd ${config.outputDir}
chmod +x deploy.sh
./deploy.sh
\`\`\`

## Configuration

The following files have been generated:

- \`serverless.yml\`: Serverless Framework configuration
- \`template.yaml\`: AWS SAM template
- \`cloudformation.yaml\`: AWS CloudFormation template
- \`src/handler.js\`: Lambda handler function
- \`package.json\`: Node.js package configuration
- \`deploy.sh\`: Deployment script

You can customize these files to adjust your deployment settings.

## Environment Variables

${config.env && Object.keys(config.env).length > 0
  ? 'The following environment variables have been configured:\n\n' +
    Object.entries(config.env).map(([key, value]) => `- ${key}: ${value}`).join('\n')
  : 'No environment variables have been configured. You can add them in the AWS Lambda console or in the configuration files.'
}

## Custom Domain

${config.domain
  ? `Your application will be available at: ${config.domain.name} (after configuring API Gateway custom domain)`
  : 'You can configure a custom domain in the API Gateway console after deployment.'
}
`;
  }

  /**
   * Optimize assets for AWS Lambda deployment
   * @param config Deployment configuration
   * @param outputDir Output directory
   * @returns Optimization results
   */
  async optimizeForDeployment(config: DeploymentConfig, outputDir: string): Promise<OptimizationResults> {
    logger.info('Optimizing assets for AWS Lambda deployment...');

    // Create public directory for static assets
    const publicDir = path.join(outputDir, 'public');
    await mkdir(publicDir, { recursive: true });

    // Initialize asset optimizer with AWS Lambda-specific options
    const optimizer = new AssetOptimizer({
      minifyJs: true,
      minifyCss: true,
      brotli: false, // AWS Lambda doesn't support Brotli out of the box
      gzip: true,
      sizeReport: true,
      terserOptions: {
        compress: {
          passes: 2,
          drop_console: false, // Keep console logs for CloudWatch
          drop_debugger: true
        },
        mangle: true,
        format: {
          comments: false
        }
      }
    });

    // Optimize all assets in the public directory
    const optimizationResults = await optimizer.optimizeDirectory(publicDir);

    // Generate and save size report
    const sizeReport = optimizer.generateSizeReport(optimizationResults);
    await writeFile(path.join(outputDir, 'size-report.txt'), sizeReport);

    return optimizationResults;
  }

  /**
   * Get AWS Lambda-specific environment variables
   * @param config Deployment configuration
   * @returns Environment variables
   */
  getEnvironmentVariables(config: DeploymentConfig): Record<string, string> {
    const env: Record<string, string> = {
      AWS_LAMBDA_FUNCTION_NAME: config.settings?.functionName || 'ordojs-app',
      NODE_ENV: 'production',
      ...config.env
    };

    // Add AWS-specific environment variables
    const awsConfig = config as AWSLambdaConfig;
    if (awsConfig.settings?.region) {
      env.AWS_REGION = awsConfig.settings.region;
    }

    return env;
  }

  /**
   * Get AWS Lambda deployment command
   * @param config Deployment configuration
   * @returns Deployment command
   */
  getDeployCommand(config: DeploymentConfig): string {
    return `cd ${config.outputDir} && ./deploy.sh`;
  }
}
