import { $TSContext, exitOnNextTick, ResourceDoesNotExistError } from '@aws-amplify/amplify-cli-core';
import { printer } from '@aws-amplify/amplify-prompts';
import inquirer from 'inquirer';
import { category } from '../../../category-constants';
import { DEPLOYMENT_MECHANISM } from '../base-api-stack';
import { GitHubSourceActionInfo } from '../pipeline-with-awaiter';
import { getAllDefaults } from '../default-values/containers-defaults';

const serviceName = 'ElasticContainer';

/**
 * Designed to be backwards compatible with the old way of representing dependencies as
 * {
 *    category: string
 *    resourceName: string
 *    attributes: string[]
 * }
 * and auto-generating environment variable names based on this info
 * When attributeEnvMap is specified, it can specify a custom environment variable name for a dependency attribute
 * If no mapping is found for an attribute in the map, then it falls back to the autogenerated value
 */
export interface ResourceDependency {
  category: string; // resource category of the dependency
  resourceName: string; // name of the dependency
  attributes: string[]; // attributes that this function depends on (must be outputs of the dependencies CFN template)
  attributeEnvMap?: { [name: string]: string }; // optional attributes to environment variable names map that will be exposed to the function
}

export enum API_TYPE {
  GRAPHQL = 'GRAPHQL',
  REST = 'REST',
}

export type ServiceConfiguration = {
  resourceName: string;
  imageSource: { type: IMAGE_SOURCE_TYPE; template?: string };
  gitHubPath: string;
  authName: string;
  gitHubToken: string;
  deploymentMechanism: DEPLOYMENT_MECHANISM;
  restrictAccess: boolean;
  dependsOn?: ResourceDependency[]; // resources this function depends on
  categoryPolicies?: any[]; // IAM policies that should be applied to this lambda
  mutableParametersState?: any; // Contains the object that is written to function-parameters.json. Kindof a hold-over from older code
  environmentMap?: Record<string, any>; // Existing function environment variable map. Should refactor to use dependsOn directly,
  gitHubInfo?: GitHubSourceActionInfo;
};

export async function serviceWalkthrough(context: $TSContext, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> {
  const allDefaultValues = getAllDefaults();

  const resourceName = await askResourceName(context, allDefaultValues);

  const containerInfo = await askContainerSource(context, resourceName, apiType);

  return { resourceName, ...containerInfo };
}

async function askResourceName(context: $TSContext, allDefaultValues: Record<string, any>) {
  const { amplify } = context;

  const { resourceName } = await inquirer.prompt([
    {
      name: 'resourceName',
      type: 'input',
      message: 'Provide a friendly name for your resource to be used as a label for this category in the project:',
      default: allDefaultValues.resourceName,
      validate: amplify.inputValidation({
        validation: {
          operator: 'regex',
          value: '^[a-z0-9]+$',
          onErrorMsg: 'Resource name should be alphanumeric with no uppercase letters',
        },
        required: true,
      }),
    },
  ]);

  return resourceName;
}

async function askContainerSource(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> {
  return newContainer(context, resourceName, apiType);
}

export enum IMAGE_SOURCE_TYPE {
  TEMPLATE = 'TEMPLATE',
  CUSTOM = 'CUSTOM',
}

async function newContainer(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise<Partial<ServiceConfiguration>> {
  let imageSource: { type: IMAGE_SOURCE_TYPE; template?: string };
  let choices = [];

  if (apiType === API_TYPE.GRAPHQL) {
    choices.push({
      name: 'ExpressJS - GraphQL template',
      value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'graphql-express' },
    });
  }

  if (apiType === API_TYPE.REST) {
    choices.push({
      name: 'ExpressJS - REST template',
      value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockerfile-rest-express' },
    });

    choices.push({
      name: 'Docker Compose - ExpressJS + Flask template',
      value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockercompose-rest-express' },
    });
  }

  choices = choices.concat([
    {
      name: 'Custom (bring your own Dockerfile or docker-compose.yml)',
      value: { type: IMAGE_SOURCE_TYPE.CUSTOM },
    },
    {
      name: 'Learn More',
      value: undefined,
    },
  ]);

  do {
    ({ imageSource } = await inquirer.prompt([
      {
        name: 'imageSource',
        type: 'list',
        message: 'What image would you like to use',
        choices,
        default: 'express_hello_world',
      },
    ]));
  } while (imageSource === undefined);

  let deploymentMechanismQuestion;

  const deploymentMechanismChoices = [
    {
      name: 'On every "amplify push" (Fully managed container source)',
      value: DEPLOYMENT_MECHANISM.FULLY_MANAGED,
    },
  ];

  if (imageSource.type === IMAGE_SOURCE_TYPE.CUSTOM) {
    deploymentMechanismChoices.push({
      name: 'On every Github commit (Independently managed container source)',
      value: DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED,
    });
  }

  deploymentMechanismChoices.push({
    name: 'Advanced: Self-managed (Learn more: docs.amplify.aws/cli/usage/containers)',
    value: DEPLOYMENT_MECHANISM.SELF_MANAGED,
  });

  do {
    deploymentMechanismQuestion = await inquirer.prompt([
      {
        name: 'deploymentMechanism',
        type: 'list',
        message: 'When do you want to build & deploy the Fargate task',
        choices: deploymentMechanismChoices,
      },
    ]);
  } while (deploymentMechanismQuestion.deploymentMechanism === 'Learn More');

  let gitHubPath: string;
  let gitHubToken: string;

  if (deploymentMechanismQuestion.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
    printer.info('We need a Github Personal Access Token to automatically build & deploy your Fargate task on every Github commit.');
    printer.info(
      'Learn more about Github Personal Access Token here: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token',
    );

    const gitHubQuestions = await inquirer.prompt([
      {
        name: 'github_access_token',
        type: 'password',
        message: 'GitHub Personal Access Token:',
      },
      {
        name: 'github_path',
        type: 'input',
        message: 'Path to your repo:',
      },
    ]);

    gitHubPath = gitHubQuestions.github_path;
    gitHubToken = gitHubQuestions.github_access_token;
  }

  const meta = context.amplify.getProjectDetails().amplifyMeta;
  const hasAccessableResources = ['storage', 'function'].some((categoryName) => {
    return Object.keys(meta[categoryName] ?? {}).length > 0;
  });
  let rolePermissions: any = {};
  if (
    hasAccessableResources &&
    (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?'))
  ) {
    rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [
      context,
      resourceName,
      undefined,
      undefined,
      category,
      serviceName,
    ]);
  }

  const { categoryPolicies, environmentMap, dependsOn, mutableParametersState } = rolePermissions;

  const restrictApiQuestion = await inquirer.prompt({
    name: 'rescrict_access',
    type: 'confirm',
    message: 'Do you want to restrict API access',
    default: true,
  });

  return {
    imageSource,
    gitHubPath,
    gitHubToken,
    deploymentMechanism: deploymentMechanismQuestion.deploymentMechanism,
    restrictAccess: restrictApiQuestion.rescrict_access,
    categoryPolicies,
    environmentMap,
    dependsOn,
    mutableParametersState,
  };
}

export async function updateWalkthrough(context: $TSContext, apiType: API_TYPE) {
  const { allResources } = await context.amplify.getResourceStatus();

  const resources = allResources
    .filter(
      (resource) =>
        resource.category === category && resource.service === serviceName && !!resource.providerPlugin && resource.apiType === apiType,
    )
    .map((resource) => resource.resourceName);

  // There can only be one appsync resource
  if (resources.length === 0) {
    const errMessage = `No ${apiType} API resource to update. Use "amplify add api" command to create a new ${apiType} API`;
    printer.error(errMessage);
    await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
    exitOnNextTick(0);
    return;
  }

  const question = [
    {
      name: 'resourceName',
      message: 'Please select the API you would want to update',
      type: 'list',
      choices: resources,
    },
  ];

  const { resourceName } = await inquirer.prompt(question);

  const resourceSettings = allResources.find(
    (resource) =>
      resource.resourceName === resourceName &&
      resource.category === category &&
      resource.service === serviceName &&
      !!resource.providerPlugin,
  );

  let { gitHubInfo: { path = undefined } = {} } = resourceSettings;
  let gitHubToken;

  if (resourceSettings.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
    if (await confirm('Would you like to change your GitHub access token')) {
      const gitHubQuestion = await inquirer.prompt({
        name: 'gitHubAccessToken',
        type: 'password',
        message: 'GitHub Personal Access Token:',
      });
      gitHubToken = gitHubQuestion.gitHubAccessToken;
    }

    if (await confirm('Would you like to change your GitHub Path to your repo')) {
      const gitHubQuestion = await inquirer.prompt({
        name: 'gitHubPath',
        type: 'input',
        message: 'Path to your repo:',
        default: path,
      });
      path = gitHubQuestion.gitHubPath;
    }
  }

  const { environmentMap = {}, mutableParametersState = {} } = resourceSettings;

  const meta = context.amplify.getProjectDetails().amplifyMeta;
  const hasAccessableResources = ['storage', 'function'].some((categoryName) => {
    return Object.keys(meta[categoryName] ?? {}).length > 0;
  });
  let rolePermissions: any = {};
  if (
    hasAccessableResources &&
    (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?'))
  ) {
    rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [
      context,
      resourceName,
      mutableParametersState.permissions,
      environmentMap,
      category,
      serviceName,
    ]);
  }

  const {
    categoryPolicies = [],
    environmentMap: newEnvironmentMap,
    dependsOn: newDependsOn = [],
    mutableParametersState: newMutableParametersState,
  } = rolePermissions;

  const { restrict_access: restrictAccess } = await inquirer.prompt({
    name: 'restrict_access',
    type: 'confirm',
    message: 'Do you want to restrict API access',
    default: resourceSettings.restrictAccess,
  });

  return {
    ...resourceSettings,
    restrictAccess,
    environmentMap: newEnvironmentMap,
    mutableParametersState: newMutableParametersState,
    dependsOn: newDependsOn,
    categoryPolicies,
    gitHubPath: path,
    gitHubToken,
  };
}

async function confirm(question: string) {
  const { confirm } = await inquirer.prompt({
    type: 'confirm',
    default: false,
    message: question,
    name: 'confirm',
  });

  return confirm;
}

export async function getPermissionPolicies(context, service, resourceName, crudOptions) {
  throw new Error('IAM access not available for this resource');
}
