/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import fs from 'node:fs';
import path from 'node:path';
import { Args, Command, Flags } from '@oclif/core';
import dotenv from 'dotenv';
import { CommonRunFlags } from '../cli/common-flags.ts';
import { ECS_WORKER_ROLE_NAME } from '../platform/aws/constants.ts';
import { parseResourceTags } from '../platform/aws/resource-tags.ts';
import PlatformECS from '../platform/aws-ecs/ecs.ts';
import runCluster from '../platform/aws-ecs/legacy/run-cluster.ts';
import { supportedRegions } from '../platform/aws-ecs/legacy/util.ts';
import { Plugin as CloudPlugin } from '../platform/cloud/cloud.ts';
import * as telemetry from '../telemetry.ts';
import generateId from '../util/generate-id.ts';

class RunCommand extends Command {
  // Untyped JS class - properties assigned dynamically
  [key: string]: any;

  static aliases = ['run:fargate', 'run:ecs', 'run-ecs'];
  // Enable multiple args:
  static strict = false;

  async run() {
    const { flags, _argv, args }: any = await this.parse(RunCommand);
    flags['platform-opt'] = [`region=${flags.region}`];

    flags.platform = 'aws:ecs';

    // Validate early, before any AWS resources are touched:
    try {
      parseResourceTags(flags['aws-tags']);
    } catch (err) {
      console.error((err as Error).message);
      process.exit(1);
    }

    if (flags.dotenv) {
      const dotEnvPath = path.resolve(process.cwd(), flags.dotenv);
      try {
        fs.statSync(dotEnvPath);
      } catch (_err) {
        console.log(`WARNING: could not read dotenv file: ${flags.dotenv}`);
      }
      dotenv.config({ path: dotEnvPath });
    }

    const testRunId = process.env.ARTILLERY_TEST_RUN_ID || generateId('t');
    global.artillery.testRunId = testRunId;

    const cloud = new CloudPlugin(null, null, { flags });
    global.artillery.cloudEnabled = cloud.enabled;
    if (cloud.enabled) {
      try {
        await cloud.init();
      } catch (caughtErr) {
        const err = caughtErr as Error;
        if (err.name === 'CloudAPIKeyMissing') {
          console.error(
            'Error: API key is required to record test results to Artillery Cloud'
          );
          console.error(
            'See https://docs.art/get-started-cloud for more information'
          );

          process.exit(7);
        } else if (err.name === 'APIKeyUnauthorized') {
          console.error(
            'Error: API key is not recognized or is not authorized to record tests'
          );

          process.exit(7);
        } else if (err.name === 'PingFailed') {
          console.error(
            'Error: unable to reach Artillery Cloud API. This could be due to firewall restrictions on your network'
          );
          console.log('Please see https://docs.art/cloud/err-ping');
          process.exit(7);
        } else {
          console.error(
            'Error: something went wrong connecting to Artillery Cloud'
          );
          console.error('Check https://status.artillery.io for status updates');
          console.error(err);
        }
      }
    }

    flags.taskRoleName = flags['task-role-name'] || ECS_WORKER_ROLE_NAME;

    const ECS = new PlatformECS(
      null,
      null,
      {},
      {
        testRunId: 'foo',
        region: flags.region,
        taskRoleName: flags.taskRoleName
      }
    );
    await ECS.init();

    process.env.USE_NOOP_BACKEND_STORE = 'true';

    telemetry.capture('run:fargate', {
      region: flags.region,
      count: flags.count
    });

    // Delegate the rest to existing implementation:
    runCluster(args.script, flags);
  }
}

RunCommand.description = `launch a test using AWS ECS/Fargate

Examples:

  To launch a test with 10 load generating workers using AWS Fargate in us-east-1:

    $ artillery run:fargate --count 10 --region us-east-1 my-test.yml
`;

RunCommand.flags = {
  ...CommonRunFlags,
  count: Flags.integer({
    description: 'Number of load generator workers to launch'
  }),
  cluster: Flags.string({
    description: 'Name of the Fargate/ECS cluster to run the test on'
  }),
  region: Flags.string({
    char: 'r',
    description: 'The AWS region to run in',
    options: supportedRegions,
    default: 'us-east-1'
  }),
  secret: Flags.string({
    multiple: true,
    description:
      'Make secrets available to workers. The secret must exist in SSM parameter store for the given region, under /artilleryio/<SECRET_NAME>'
  }),
  'launch-type': Flags.string({
    description: 'The launch type to use for the test. Defaults to Fargate.',
    options: ['ecs:fargate', 'ecs:ec2']
  }),
  spot: Flags.boolean({
    description:
      'Use Fargate Spot (https://docs.art/fargate-spot) Ignored when --launch-type is set to ecs:ec2'
  }),
  'launch-config': Flags.string({
    description:
      'JSON to customize launch configuration of ECS/Fargate tasks (see https://www.artillery.io/docs/reference/cli/run-fargate#using---launch-config)'
  }),
  'container-dns-servers': Flags.string({
    description:
      'Comma-separated list of DNS servers for Artillery container. Maps to dnsServers parameter in ECS container definition'
  }),
  'task-ephemeral-storage': Flags.string({
    description:
      'Ephemeral storage in GiB for the worker task. Maps to ephemeralStorage parameter in ECS container definition. Fargate-only.',
    type: 'integer'
  } as any),

  'subnet-ids': Flags.string({
    description:
      'Comma-separated list of AWS VPC subnet IDs to launch Fargate tasks in'
  }),
  'security-group-ids': Flags.string({
    description:
      'Comma-separated list of AWS VPC security group IDs to launch Fargate tasks in'
  }),
  'task-role-name': Flags.string({
    description: 'Custom IAM role name for Fargate containers to assume'
  }),
  cpu: Flags.string({
    description:
      'Set task vCPU on Fargate (defaults to 4 vCPU). Value may be set as a number of vCPUs between 1-16 (e.g. 4), or as number of vCPU units (e.g. 4096).'
  }),
  memory: Flags.string({
    description:
      'Set task memory on Fargate (defaults to 8 GB). Value may be set as number of GB between 1-120 (e.g. 8), or as MiB (e.g. 8192)'
  }),
  packages: Flags.string({
    description:
      'Path to package.json file which lists dependencies for the test script'
  }),
  'max-duration': Flags.string({
    description: 'Maximum duration of the test run'
  }),
  'aws-tags': Flags.string({
    description:
      'Comma-separated list of tags in key:value format to apply to AWS resources created for the test run (Fargate tasks and SQS queue), for example: --aws-tags "team:perf,cost-center:1234"'
  }),
  'no-assign-public-ip': Flags.boolean({
    description:
      'Turn off the default behavior of assigning public IPs to Fargate worker tasks. When this option is used you must make sure tasks have a route to the internet, i.e. via a NAT gateway attached to a private subnet',
    default: false
  })
};

RunCommand.args = {
  script: Args.string()
};

export default RunCommand;
