import * as logSymbols from 'log-symbols';
import * as chalk from 'chalk';
import { join } from 'path';
import { Choice } from 'prompts';
import { CliTerseError, CliUsageError } from '@alwaysai/alwayscli';
import { readdirSync } from 'fs-extra';
import * as tempy from 'tempy';

import { PLEASE_REPORT_THIS_ERROR_MESSAGE } from '../../constants';
import {
  RequiredWithYesMessage,
  promptForInput,
  logger,
  echo,
  JsSpawner,
  copyFiles,
  fetchFilestream
} from '../../util';
import { confirmPromptComponent } from '../general';
import { getProjectByUUID } from '../../core/project';
import { CliAuthenticationClient, CliRpcClient } from '../../infrastructure';
import { stringifyError } from '../../util/stringify-error';

const BASE_URL = 'https://github.com/alwaysai/';
const ARCHIVE_PATH = 'archive/refs/heads/main.tar.gz';
const STARTER_APPS = [
  {
    webName: 'realtime_object_detector',
    purpose: 'Object Detection',
    downloadUrl: `${BASE_URL}object-detector/${ARCHIVE_PATH}`
  },
  {
    webName: 'detector_tracker',
    purpose: 'Object Detection with Tracking',
    downloadUrl: `${BASE_URL}detector-tracker/${ARCHIVE_PATH}`
  },
  {
    webName: 'image_classifier',
    purpose: 'Image Classification',
    downloadUrl: `${BASE_URL}image-classifier/${ARCHIVE_PATH}`
  },
  {
    webName: 'semantic_segmentation_voc',
    purpose: 'Semantic Segmentation',
    downloadUrl: `${BASE_URL}semantic-segmentation-voc/${ARCHIVE_PATH}`
  },
  {
    webName: 'realtime_pose_estimator',
    purpose: 'Human Pose Estimation',
    downloadUrl: `${BASE_URL}pose-estimator/${ARCHIVE_PATH}`
  },
  {
    webName: 'hello_world_face_detector',
    purpose: 'Face Detector',
    downloadUrl: `${BASE_URL}face-detector/${ARCHIVE_PATH}`
  },
  {
    webName: 'instance_segmentation',
    purpose: 'Instance Segmentation',
    downloadUrl: `${BASE_URL}instance-segmentation/${ARCHIVE_PATH}`
  }
];

export async function projectSelectComponent(props: {
  yes: boolean;
  projectUuid?: string;
}) {
  const { yes, projectUuid } = props;
  if (yes && !projectUuid) {
    throw new CliUsageError(RequiredWithYesMessage('project'));
  }
  const project = projectUuid
    ? await getProjectByUUID(projectUuid)
    : await selectFromListOfProjects();
  if (project.starter_app) {
    let appName = project.starter_app;

    // Replace legacy "hello_world" starter app with face detector on outdated projects
    if (appName === 'hello_world') {
      appName = 'hello_world_face_detector';
    }

    await downloadStarterApp({ yes, appName });
  }
  return project;
}

async function selectFromListOfProjects() {
  const authInfo = await CliAuthenticationClient().getInfo();

  const projects = await CliRpcClient().listProjectsUserIsCollaborator({
    user_name: authInfo.username
  });

  const choices: Choice[] = [
    { title: chalk.green.bold('Create new project'), value: 'new' }
  ];
  projects.forEach((project, index) => {
    choices.push({
      title: project.name,
      value: `${index}`
    });
  });

  const choice = await promptForInput({
    purpose: 'to select a project',
    questions: [
      {
        type: 'select',
        name: 'project',
        message: 'Select a project or create a new project',
        initial: 0,
        choices
      }
    ]
  });

  if (choice.project === 'new') {
    return await createProject();
  }
  return projects[choice.project];
}

async function createProject() {
  const { username } = await CliAuthenticationClient().getInfo();
  const team = await CliRpcClient().listTeamsUserIsOwner({
    user_name: username
  });

  // TODO: Need to limit input characters to match dashboard implementation
  const input = await promptForInput({
    purpose: 'to choose a project name',
    questions: [
      {
        type: 'text',
        name: 'name',
        message: 'Enter a project name:'
      }
    ]
  });
  const name = input.name;

  const choice = await promptForInput({
    purpose: 'to select a project type',
    questions: [
      {
        type: 'select',
        name: 'type',
        message: 'How would you like to initialize your project?',
        initial: 0,
        choices: [
          { title: 'From a Starter App', value: 'starterApp' },
          { title: 'As an empty app', value: 'empty' }
        ]
      }
    ]
  });

  let starterApp = '';

  switch (choice.type) {
    case 'starterApp': {
      const choices: Choice[] = [];
      STARTER_APPS.forEach((app) => {
        choices.push({
          title: app.purpose,
          value: app.webName
        });
      });
      const choice = await promptForInput({
        purpose: 'to select a starter app',
        questions: [
          {
            type: 'select',
            name: 'app',
            message: 'Select a Starter App',
            initial: 0,
            choices
          }
        ]
      });
      starterApp = choice.app;
      break;
    }
    case 'empty':
    default:
    // Do nothing
  }

  const project = await CliRpcClient().createProject({
    owner: username,
    team_id: team[0].id,
    name,
    description: '',
    git_url: '',
    starter_app: starterApp
  });

  return project;
}

async function checkDirIsEmpty(dir) {
  const fileNames = readdirSync(dir);
  return fileNames.length === 0;
}

async function downloadStarterApp(props: { yes: boolean; appName: string }) {
  const { yes, appName } = props;
  let url: string | undefined = undefined;
  if ((await checkDirIsEmpty(process.cwd())) === false) {
    const proceed =
      yes ||
      (await confirmPromptComponent({
        message:
          'Would you like to download the Starter App? Files in this directory may be overwritten!'
      }));
    if (!proceed) {
      echo('Skipping download of starter app!');
      return;
    }
  }
  for (const app of STARTER_APPS) {
    if (app.webName === appName) {
      url = app.downloadUrl;
      break;
    }
  }
  if (url === undefined) {
    throw new CliTerseError(`Starter App "${appName}" not found!`);
  }
  const tmpDir = tempy.directory();
  try {
    const fileStream = await fetchFilestream(url);
    const spawner = JsSpawner({ path: tmpDir });
    await spawner.untar(fileStream);
  } catch (e) {
    logger.error(stringifyError(e));
    throw new CliTerseError(
      `Failed to download "${url}", check your internet connection and retry`
    );
  }

  const fileNames = readdirSync(tmpDir);
  if (fileNames.length !== 1 || !fileNames[0]) {
    logger.error(
      `Expected application to contain a single directory: ${fileNames}`
    );
    throw new CliTerseError(
      `Expected application to contain a single directory. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const srcSpawner = JsSpawner({ path: join(tmpDir, fileNames[0]) });
  const destSpawner = JsSpawner({ path: process.cwd() });
  await copyFiles(srcSpawner, destSpawner);
  await srcSpawner.rimraf();
  echo(`${logSymbols.success} Download Starter App`);
}
