<template>
	<Dialog
		v-model="show"
		:options="{
			size: '4xl',
			title: '更新站点分组',
		}"
	>
		<template #body-content>
			<AlertBanner
				v-if="benchDocResource.pg.are_builds_suspended"
				class="mb-4"
				title="<b>构建已暂停：</b>更新将在构建恢复后安排运行。"
				type="warning"
			/>
			<!-- 更新步骤 -->
			<div class="space-y-4">
				<!-- 选择应用步骤 -->
				<div v-if="step === 'select-apps'">
					<h2 class="mb-4 text-lg font-medium">选择要更新的应用</h2>
					<GenericList
						class="max-h-[500px]"
						v-if="benchDocResource.pg.deploy_information.update_available"
						:options="updatableAppOptions"
						@update:selections="handleAppSelection"
					/>
					<p v-else class="text-center text-base text-gray-600">
						没有可更新的应用
					</p>
				</div>

				<!-- 移除应用步骤 -->
				<div v-else-if="step === 'removed-apps'">
					<h2 class="mb-4 text-lg font-medium">这些应用将被移除</h2>
					<GenericList class="max-h-[500px]" :options="removedAppOptions" />
				</div>

				<!-- 选择站点步骤 -->
				<div v-else-if="step === 'select-sites'">
					<h2 class="mb-4 text-lg font-medium">选择要更新的站点</h2>
					<GenericList
						class="max-h-[500px]"
						v-if="benchDocResource.pg.deploy_information.sites.length"
						:options="siteOptions"
						@update:selections="handleSiteSelection"
					/>
					<p
						class="text-center text-base font-medium text-gray-600"
						v-else-if="!benchDocResource.pg.deploy_information.sites.length"
					>
						没有可更新的活跃站点
					</p>
				</div>

				<!-- 限制构建步骤 -->
				<div
					v-else-if="step === 'restrict-build' && restrictMessage"
					class="flex flex-col gap-4"
				>
					<div class="flex items-center gap-2">
						<h2 class="text-lg font-medium">构建可能会失败</h2>
						<a
							href="https://jingrow.com/docs/common-issues/build-might-fail"
							target="_blank"
							class="cursor-pointer rounded-full border border-gray-200 bg-gray-100 p-0.5 text-base text-gray-700"
						>
							<i-lucide-help-circle :class="`h-4 w-4 text-red-600`" />
						</a>
					</div>
					<p
						class="text-base font-medium text-gray-800"
						v-html="restrictMessage"
					></p>
					<div class="mt-4">
						<FormControl
							label="我明白，仍然运行部署"
							type="checkbox"
							v-model="ignoreWillFailCheck"
						/>
					</div>
				</div>

				<div v-if="canUpdateInPlace" class="flex gap-2">
					<FormControl
						label="使用原地更新"
						type="checkbox"
						v-model="useInPlaceUpdate"
					/>
					<Tooltip text="查看文档">
						<a
							href="https://jingrow.com/docs/in-place-updates"
							target="_blank"
						>
							<i-lucide-help-circle :class="`h-4 w-4 text-gray-600`" />
						</a>
					</Tooltip>
				</div>

				<ErrorMessage :message="errorMessage" />
			</div>
		</template>
		<template #actions>
			<div class="flex items-center justify-between space-y-2">
				<div v-if="!canShowBack"><!-- 占位div --></div>
				<Button v-if="canShowBack" label="返回" @click="back" />
				<Button v-if="canShowNext" variant="solid" label="下一步" @click="next" />
				<Button
					v-if="canShowDeploy"
					variant="solid"
					:label="deployLabel"
					:loading="
						$resources.deployAndUpdate.loading ||
						$resources.updateInPlace.loading
					"
					@click="updateBench"
				/>
			</div>
		</template>
	</Dialog>
</template>

<script>
import { h } from 'vue';
import { Checkbox, getCachedDocumentResource } from 'jingrow-ui';
import CommitChooser from '@/components/utils/CommitChooser.vue';
import CommitTag from '@/components/utils/CommitTag.vue';
import GenericList from '../../components/GenericList.vue';
import { getTeam } from '../../data/team';
import { DashboardError } from '../../utils/error';
import AlertBanner from '../AlertBanner.vue';

export default {
	name: 'UpdateReleaseGroupDialog',
	props: ['bench'],
	components: {
		GenericList,
		CommitChooser,
		CommitTag,
		AlertBanner,
	},
	data() {
		return {
			show: true,
			step: '',
			errorMessage: '',
			ignoreWillFailCheck: false,
			useInPlaceUpdate: false,
			restrictMessage: '',
			selectedApps: [],
			selectedSites: [],
		};
	},
	mounted() {
		if (this.hasUpdateAvailable) {
			this.step = '选择应用';
		} else if (this.hasRemovedApps) {
			this.step = '已移除应用';
		} else {
			this.step = '选择站点';
		}
	},
	computed: {
		updatableAppOptions() {
			let deployInformation = this.benchDocResource.pg.deploy_information;
			let appData = deployInformation.apps.filter(
				(app) => app.update_available === true,
			);

			return {
				data: appData,
				selectable: true,
				columns: [
					{
						label: '应用',
						fieldname: 'title',
						width: 1.75,
					},
					{
						label: '从',
						fieldname: 'current_hash',
						type: 'Component',
						component({ row: app }) {
							if (!app.current_hash) return null;
							let tag = app.will_branch_change
								? app.current_branch
								: app.current_hash.slice(0, 7);

							return h(CommitTag, {
								tag: tag,
								link: `${app.repository_url}/commit/${tag}`,
							});
						},
					},
					{
						label: '到',
						fieldname: 'next_release',
						type: 'Component',
						component({ row: app }) {
							if (app.will_branch_change) {
								return h(CommitTag, {
									tag: app.branch,
									link: `${app.repository_url}/commit/${app.branch}`,
								});
							}

							function commitChooserOptions(app) {
								return app.releases.map((release) => {
									const messageMaxLength = 75;
									let message = release.message.split('\n')[0];
									message =
										message.length > messageMaxLength
											? message.slice(0, messageMaxLength) + '...'
											: message;

									return {
										label: release.tag
											? release.tag
											: `${message} (${release.hash.slice(0, 7)})`,
										value: release.name,
									};
								});
							}

							function initialDeployTo(app) {
								const next_release = app.releases.filter(
									(release) => release.name === app.next_release,
								)[0];
								if (app.will_branch_change) {
									return app.branch;
								} else if (next_release) {
									return next_release.tag || next_release.hash.slice(0, 7);
								}
							}

							if (!app.releases.length) return undefined;

							let initialValue = {
								label: initialDeployTo(app),
								value: app.next_release,
							};

							return h(CommitChooser, {
								options: commitChooserOptions(app),
								modelValue: initialValue,
								'onUpdate:modelValue': (value) => {
									appData.find((a) => a.name === app.name).next_release =
										value.value;
								},
							});
						},
					},
					{
						label: '状态',
						fieldname: 'title',
						type: 'Badge',
						format(value, row) {
							if (
								deployInformation.removed_apps.find(
									(app) => app.name === row.name,
								)
							) {
								return '将被卸载';
							} else if (!row.will_branch_change && !row.current_hash) {
								return '首次部署';
							}
							return '有更新可用';
						},
					},
					{
						label: '变更',
						type: '按钮',
						width: 0.5,
						align: 'right',
						Button({ row }) {
							let url;
							if (row.current_hash && row.next_release) {
								let hash = row.releases.find(
									(release) => release.name === row.next_release,
								)?.hash;

								if (hash)
									url = `${row.repository_url}/compare/${row.current_hash}...${hash}`;
} else if (row.next_release) {
								url = `${row.repository_url}/commit/${
									row.releases.find(
										(release) => release.name === row.next_release,
									).hash
								}`;
							}

							if (!url) return null;

							return {
								label: '查看',
								variant: 'ghost',
								onClick() {
									window.open(url, '_blank');
								},
							};
						},
					},
				],
			};
		},
		removedAppOptions() {
			let deployInformation = this.benchDocResource.pg.deploy_information;
			let appData = deployInformation.removed_apps;

			return {
				data: appData,
				columns: [
					{
						label: '应用',
						fieldname: 'title',
					},
					{
						label: '状态',
						fieldname: 'name',
						type: 'Badge',
						format() {
							return '将被卸载';
						},
					},
				],
			};
		},
		siteOptions() {
			let deployInformation = this.benchDocResource.pg.deploy_information;
			let siteData = deployInformation.sites;
			let team = getTeam();

			/**
			 * 如果使用就地更新，失败的补丁将被跳过
			 * 并且默认情况下不会进行站点备份。
			 */
			const disabled = this.useInPlaceUpdate;

			return {
				data: siteData,
				selectable: true,
				columns: [
					{
						label: '站点',
						fieldname: 'name',
					},
					{
						label: '跳过失败的补丁',
						fieldname: 'skip_failing_patches',
						width: 0.5,
						type: 'Component',
						component({ row }) {
							return h(Checkbox, {
								modelValue: row.skip_failing_patches,
								disabled,
							});
						},
					},
					{
						label: '跳过备份',
						fieldname: 'skip_backups',
						width: 0.3,
						type: 'Component',
						condition() {
							return !!team.pg.skip_backups;
						},
						component({ row }) {
							return h(Checkbox, {
								modelValue: row.skip_backups,
								disabled,
							});
						},
					},
				],
			};
		},
		benchDocResource() {
			return getCachedDocumentResource('Release Group', this.bench);
		},
		hasUpdateAvailable() {
			return this.benchDocResource.pg.deploy_information.apps.some(
				(app) => app.update_available === true,
			);
		},
		hasRemovedApps() {
			return !!this.benchDocResource.pg.deploy_information.removed_apps.length;
		},
		deployInformation() {
			return this.benchDocResource?.pg.deploy_information;
		},
		canShowBack() {
			if (this.step === 'select-apps') {
				return false;
			}

			return this.hasUpdateAvailable || this.step === 'restrict-build';
		},
		canShowNext() {
			if (this.step === 'restrict-build') {
				return false;
			}

			if (this.step === 'select-sites' && !this.restrictMessage) {
				return false;
			}

			return true;
		},
		canShowDeploy() {
			return !this.canShowNext;
		},
		deployLabel() {
			if (this.selectedSites.length === 0) {
				return '跳过并部署';
			}

			let site = '站点';
			if (this.selectedSites.length > 1) {
				site = `${this.selectedSites.length} 个站点`;
			}

			if (this.useInPlaceUpdate) {
				return `就地更新 ${site}`;
			}

			return `部署并更新 ${site}`;
		},
		canUpdateInPlace() {
			if (!this.benchDocResource?.pg?.enable_inplace_updates) {
				return false;
			}

			// 就地更新不能与已移除的应用一起进行。
			if (this.hasRemovedApps) {
				return false;
			}

			// 所有要更新的站点必须属于同一个工作台。
			const benches = new Set(this.selectedSites.map((s) => s.bench));
			if (benches.size !== 1) {
				return false;
			}

			// 就地更新失败的工作台必须进行常规更新。
			const inPlaceUpdateFailedBenches =
				this.benchDocResource?.pg?.inplace_update_failed_benches ?? [];

			const allSites = this.siteOptions.data
				.filter(
					(s) =>
						benches.has(s.bench) ||
						inPlaceUpdateFailedBenches.includes(s.bench),
				)
.map((s) => s.name);

			// 工作台下的所有站点都应更新
			if (allSites.length !== this.selectedSites.length) {
				return false;
			}

			return true;
		},
	},
	resources: {
		deployAndUpdate() {
			return {
				url: 'jcloud.api.bench.deploy_and_update',
				params: {
					name: this.bench,
					apps: this.selectedApps,
					sites: this.selectedSites,
					run_will_fail_check: !this.ignoreWillFailCheck,
				},
				validate() {
					if (
						this.hasUpdateAvailable &&
						this.selectedApps.length === 0 &&
						this.deployInformation.removed_apps.length === 0
					) {
						throw new DashboardError('请选择一个应用以继续');
					}
				},
				onSuccess(candidate) {
					this.$router.push({
						name: 'Deploy Candidate',
						params: {
							id: candidate,
							name: this.bench,
						},
					});
					this.restrictMessage = '';
					this.show = false;
					this.$emit('success', candidate);
				},
				onError: this.setErrorMessage.bind(this),
			};
		},
		updateInPlace() {
			return {
				url: 'jcloud.api.bench.update_inplace',
				params: {
					name: this.bench,
					apps: this.selectedApps,
					sites: this.selectedSites,
				},
				onSuccess(id) {
					this.$router.push({
						name: 'Release Group Job',
						params: { id },
					});

					this.restrictMessage = '';
					this.show = false;
					this.$emit('success', null);
				},
				onError: this.setErrorMessage.bind(this),
			};
		},
	},
	methods: {
		back() {
			if (this.step === 'select-apps') {
				return;
			} else if (this.step === 'removed-apps') {
				this.step = 'select-apps';
			} else if (this.step === 'select-sites' && this.hasRemovedApps) {
				this.step = 'removed-apps';
			} else if (this.step === 'select-sites' && !this.hasRemovedApps) {
				this.step = 'select-apps';
			} else if (this.step === 'restrict-build') {
				this.step = 'select-sites';
			}

			if (this.step === 'select-apps') {
				this.selectedApps = [];
			}
		},
		next() {
			if (this.errorMessage) {
				this.errorMessage = '';
			}

			if (this.step === 'select-apps' && this.selectedApps.length === 0) {
				this.errorMessage = '请选择一个应用以继续';
				return;
			} else if (this.step === 'select-apps' && this.hasRemovedApps) {
				this.step = 'removed-apps';
			} else if (this.step === 'select-apps' && !this.hasRemovedApps) {
				this.step = 'select-sites';
			} else if (this.step === 'removed-apps') {
				this.step = 'select-sites';
			} else if (this.step === 'select-sites' && this.restrictMessage) {
				this.step = 'restrict-build';
			}
		},
		handleAppSelection(apps) {
			apps = Array.from(apps);
			let appData = this.benchDocResource.pg.deploy_information.apps;

			this.selectedApps = appData
				.filter((app) => apps.includes(app.name))
				.map((app) => {
					return {
						app: app.name,
						source: app.source,
						release: app.next_release,
						hash: app.releases.find(
							(release) => release.name === app.next_release,
						).hash,
					};
				});
		},
		handleSiteSelection(sites) {
			sites = Array.from(sites);
			let siteData = this.benchDocResource.pg.deploy_information.sites;

			this.selectedSites = siteData.filter((site) => sites.includes(site.name));
		},
		deployFrom(app) {
			if (app.will_branch_change) {
				return app.current_branch;
			}
			return app.current_hash
				? app.current_tag || app.current_hash.slice(0, 7)
				: null;
		},
		initialDeployTo(app) {
			return this.benchDocResource.pg.deploy_information.apps.find(
				(a) => a.app === app.app,
			).next_release;
		},
		updateBench() {
			if (this.restrictMessage && !this.ignoreWillFailCheck) {
				this.errorMessage = '请勾选<b>我理解</b>复选框以继续';
				return;
			}

			this.errorMessage = '';
			if (this.canUpdateInPlace && this.useInPlaceUpdate) {
				this.setSkipBackupsAndFailingPatches();
				this.$resources.updateInPlace.submit();
			} else {
				this.$resources.deployAndUpdate.submit();
			}
		},
		setSkipBackupsAndFailingPatches() {
			for (const site of this.selectedSites) {
				site.skip_failing_patches = true;
				site.skip_backups = true;
			}
		},
		setErrorMessage(error) {
			this.ignoreWillFailCheck = false;
			if (error?.exc_type === 'BuildValidationError') {
				this.restrictMessage = error?.messages?.[0] ?? '';
			}

			if (error?.exc_type === 'PermissionError') {
				this.errorMessage = error?.messages?.[0] ?? '';
				return;
			}

			if (this.restrictMessage) {
				this.step = 'restrict-build';
				return;
			}

			this.errorMessage =
				'内部服务器错误：无法启动部署';
		},
	},
};
</script>