/**
 * @license
 * Copyright 2016-2020 Balena Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Flags, Args, Command } from '@oclif/core';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';

export default class OsDownloadCmd extends Command {
	public static description = stripIndent`
		Download an unconfigured OS image.

		Download an unconfigured OS image for the specified device type.
		Check available device types with 'balena device-type list'.

		Note: Currently this command only works with balenaCloud, not openBalena.
		If using openBalena, please download the OS from: https://www.balena.io/os/

		The '--version' option is used to select the balenaOS version. If omitted,
		the latest released version is downloaded (and if only pre-release versions
		exist, the latest pre-release version is downloaded).

		Use '--type' to specify the type of OS download
		If omitted, the default type for the specified device type-version combination is used.
		Some OS download types may not be available for certain device types and versions.

		Use '--version menu' or '--version menu-esr' to interactively select the
		OS version. The latter lists ESR versions which are only available for
		download on Production and Enterprise plans. See also:
		https://www.balena.io/docs/reference/OS/extended-support-release/

		Development images can be selected by appending \`.dev\` to the version.
`;
	public static examples = [
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version 2.101.7',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version 2022.7.0',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version ^2.90.0',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version 2.60.1+rev1',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version 2.60.1+rev1.dev',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version 2021.10.2.prod',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version latest',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version menu',
		'$ balena os download raspberrypi3 -o ../foo/bar/raspberrypi3.img --version menu-esr',
		'$ balena os download generic-amd64 -o ../foo/bar/generic-amd64.img --type installation-media',
	];

	public static args = {
		type: Args.string({
			description: 'the device type',
			required: true,
		}),
	};

	public static flags = {
		output: Flags.string({
			description: 'output path',
			char: 'o',
			required: true,
		}),
		version: Flags.string({
			description: stripIndent`
				version number (ESR or non-ESR versions),
				or semver range (non-ESR versions only),
				or 'latest' (excludes invalidated & pre-releases),
				or 'menu' (interactive menu, non-ESR versions),
				or 'menu-esr' (interactive menu, ESR versions)
				`,
		}),
		type: Flags.string({
			options: ['installation-media', 'disk-image'],
			description: stripIndent`
				'disk-image' (for flashing onto device system disk/storage)
				or 'installation-media' (for creating installation media to automatically erase, format, and install balenaOS on a device)
				`,
		}),
	};

	public async run() {
		const { args: params, flags: options } = await this.parse(OsDownloadCmd);
		const { downloadOSImage, isESR } = await import('../../utils/os');

		const balena = getBalenaSdk();
		// TODO: Check the OS release's assets for a file that would start with `balena-image-flasher-${DEVICE_TYPE_SLUG}.manifest` once they have been backfilled
		const dtManifest = await balena.models.config.getDeviceTypeManifestBySlug(
			params.type,
		);
		const defaultImageType = /^(resin|balena)-image-flasher\b/.test(
			dtManifest.yocto.deployArtifact,
		)
			? 'installation-media'
			: 'disk-image';

		if (
			options.type === 'installation-media' ||
			(defaultImageType === 'installation-media' && !options.type)
		) {
			// If the user is downloading an installation-media image, warn them that an installation-media image will wipe the target device's storage
			// This is because some users may not understand the difference between an installation-media and disk-image, and may be surprised that the downloaded image will wipe their device when flashed
			// Additionally, when no type is specified, the type of image downloaded depends on the device type, and some device types default to installation-media images, so we want to warn in that case as well
			console.warn(
				"WARNING: balenaOS installation media automatically and without confirmation erases and formats the target device's internal storage when booted.",
			);
		}

		// balenaOS ESR versions require user authentication
		if (options.version) {
			if (options.version === 'menu-esr' || isESR(options.version)) {
				try {
					const { checkLoggedIn } = await import('../../utils/patterns');
					await checkLoggedIn();
				} catch (e) {
					const { ExpectedError, NotLoggedInError } =
						await import('../../errors');
					if (e instanceof NotLoggedInError) {
						throw new ExpectedError(stripIndent`
							${e.message}
							User authentication is required to download balenaOS ESR versions.`);
					}
					throw e;
				}
			}
		}

		try {
			await downloadOSImage(
				params.type,
				options.output,
				options.version,
				defaultImageType === options.type
					? undefined
					: (options.type as 'installation-media' | 'disk-image' | undefined),
			);
		} catch (e) {
			e.deviceTypeSlug = params.type;
			e.message ??= '';
			const { OSVersionNotFoundError } = await import('../../errors');
			if (e instanceof OSVersionNotFoundError) {
				const version = options.version ?? '';
				if (
					!version.endsWith('.dev') &&
					!version.endsWith('.prod') &&
					/^v?\d+\.\d+\.\d+/.test(version)
				) {
					e.message += `
** Hint: some OS releases require specifying the full OS version including
** the '.prod' or '.dev' suffix, e.g. '--version 2021.10.2.prod'`;
				}
			}
			throw e;
		}
	}
}
