// Copyright Project Harbor Authors
//
// 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 { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { ScanningMetrics, Triggers } from '../../config/config';
import { ErrorHandler } from '../../../../shared/units/error-handler';
import { TranslateService } from '@ngx-translate/core';
import { ScanAllRepoService } from './scanAll.service';
import { OriginCron } from '../../../../shared/services';
import { CronScheduleComponent } from '../../../../shared/components/cron-schedule';
import {
    DATABASE_UPDATED_PROPERTY,
    VULNERABILITY_SCAN_STATUS,
} from '../../../../shared/units/utils';
import { DatePipe } from '@angular/common';
import { errorHandler } from '../../../../shared/units/shared.utils';
import { ScanAllService } from '../../../../../../ng-swagger-gen/services/scan-all.service';
import {
    SkipSessionRenewalService,
    skipSessionRenewal,
} from '../../../../services/skip-session-renewal.service';

const SCHEDULE_TYPE_NONE = 'None';
const TIMEOUT: number = 5000;

@Component({
    selector: 'vulnerability-config',
    templateUrl: './vulnerability-config.component.html',
    styleUrls: ['./vulnerability-config.component.scss'],
})
export class VulnerabilityConfigComponent implements OnInit, OnDestroy {
    onGoing: boolean;
    originCron: OriginCron;
    schedule: any;
    onSubmitting: boolean = false;
    openState: boolean = false;
    getLabelCurrent: string;

    @ViewChild(CronScheduleComponent)
    CronScheduleComponent: CronScheduleComponent;
    gettingMetrics: boolean;
    private _scanningMetrics: ScanningMetrics;
    totalWidth: number = 200;
    i18nKeyMap = {
        Pending: 'CONFIG.SCANNING.STATUS.PENDING',
        Running: 'CONFIG.SCANNING.STATUS.RUNNING',
        Stopped: 'CONFIG.SCANNING.STATUS.STOPPED',
        Error: 'CONFIG.SCANNING.STATUS.ERROR',
        Success: 'CONFIG.SCANNING.STATUS.SUCCESS',
        Scheduled: 'CONFIG.SCANNING.STATUS.SCHEDULED',
    };
    updatedTimeStr: string;
    onGettingUpdatedTimeStr: boolean;
    hasDefaultScanner: boolean = false;
    private _timeout: any;

    constructor(
        private scanningService: ScanAllRepoService,
        private newScanAllService: ScanAllService,
        private errorHandlerEntity: ErrorHandler,
        private translate: TranslateService,
        private skipSessionRenewalService: SkipSessionRenewalService
    ) {}

    get scanningMetrics(): ScanningMetrics {
        return this._scanningMetrics;
    }

    set scanningMetrics(metrics: ScanningMetrics) {
        this._scanningMetrics = metrics;
    }

    get scanAvailable(): boolean {
        return (
            !this.onSubmitting &&
            !this.gettingMetrics &&
            !this.isOnScanning() &&
            this.hasDefaultScanner
        );
    }

    getScanText() {
        this.translate
            .get('CONFIG.SCANNING.SCHEDULE_TO_SCAN_ALL')
            .subscribe((res: string) => {
                this.getLabelCurrent = res;
            });
    }

    getSchedule() {
        this.onGoing = true;
        this.scanningService
            .getSchedule()
            .pipe(
                finalize(() => {
                    this.onGoing = false;
                })
            )
            .subscribe(
                schedule => {
                    this.initSchedule(schedule);
                },
                error => {
                    this.errorHandlerEntity.error(error);
                }
            );
    }

    getScanners() {
        this.onGettingUpdatedTimeStr = true;
        this.scanningService.getScanners().subscribe(
            scanners => {
                let flag = false;
                if (scanners && scanners.length) {
                    scanners.forEach(scanner => {
                        if (scanner.is_default) {
                            flag = true;
                            this.getMetrics();
                            this.getSchedule();
                            this.getScannerMetadata(scanner.uuid);
                        }
                    });
                }
                if (flag) {
                    this.hasDefaultScanner = true;
                }
                if (!flag) {
                    this.onGettingUpdatedTimeStr = false;
                    this.translate
                        .get('SCANNER.NO_DEFAULT_SCANNER')
                        .subscribe(res => {
                            this.errorHandlerEntity.warning(res);
                        });
                }
            },
            error => {
                this.onGettingUpdatedTimeStr = false;
            }
        );
    }

    getScannerMetadata(uid: string) {
        this.scanningService
            .getScannerMetadata(uid)
            .pipe(finalize(() => (this.onGettingUpdatedTimeStr = false)))
            .subscribe(metadata => {
                if (metadata && metadata.properties) {
                    for (let key in metadata.properties) {
                        if (
                            key === DATABASE_UPDATED_PROPERTY &&
                            metadata.properties[key]
                        ) {
                            this.updatedTimeStr = new DatePipe(
                                'en-us'
                            ).transform(metadata.properties[key], 'short');
                        }
                    }
                }
            });
    }

    public initSchedule(schedule: any) {
        if (schedule && schedule.schedule !== null) {
            this.schedule = schedule;
            this.originCron = this.schedule.schedule;
        } else {
            this.originCron = {
                type: SCHEDULE_TYPE_NONE,
                cron: '',
            };
        }
    }

    ngOnInit(): void {
        this.getScanText();
        this.getScanners();
    }

    ngOnDestroy() {
        if (this._timeout) {
            clearTimeout(this._timeout);
            this._timeout = null;
        }
    }

    isOnScanning(): boolean {
        return this.scanningMetrics && this.scanningMetrics.ongoing;
    }

    getMetrics() {
        this.gettingMetrics = true;
        this.scanningService
            .getMetrics()
            .pipe(skipSessionRenewal(this.skipSessionRenewalService))
            .pipe(finalize(() => (this.gettingMetrics = false)))
            .subscribe(response => {
                if (response) {
                    if (response.ongoing) {
                        if (this._timeout) {
                            clearTimeout(this._timeout);
                            this._timeout = null;
                        }
                        if (!this._timeout) {
                            this._timeout = setTimeout(() => {
                                this.getMetrics();
                            }, TIMEOUT);
                        }
                    }
                    this.scanningMetrics = response;
                }
            });
    }

    getI18nKey(str: string): string {
        if (str && this.i18nKeyMap[str]) {
            return this.i18nKeyMap[str];
        }
        return str;
    }

    errorWidth() {
        if (
            this.scanningMetrics &&
            this.scanningMetrics.metrics &&
            this.scanningMetrics.total &&
            this.scanningMetrics.metrics[VULNERABILITY_SCAN_STATUS.ERROR]
        ) {
            return (
                (this.scanningMetrics.metrics[VULNERABILITY_SCAN_STATUS.ERROR] /
                    this.scanningMetrics.total) *
                    this.totalWidth +
                'px'
            );
        }
        return '0';
    }

    finishedWidth() {
        if (
            this.scanningMetrics &&
            this.scanningMetrics.metrics &&
            this.scanningMetrics.total &&
            this.scanningMetrics.metrics[VULNERABILITY_SCAN_STATUS.SUCCESS]
        ) {
            return (
                (this.scanningMetrics.metrics[
                    VULNERABILITY_SCAN_STATUS.SUCCESS
                ] /
                    this.scanningMetrics.total) *
                    this.totalWidth +
                'px'
            );
        }
        return '0';
    }

    runningWidth() {
        if (
            this.scanningMetrics &&
            this.scanningMetrics.metrics &&
            this.scanningMetrics.total &&
            this.scanningMetrics.metrics[VULNERABILITY_SCAN_STATUS.RUNNING]
        ) {
            return (
                (this.scanningMetrics.metrics[
                    VULNERABILITY_SCAN_STATUS.RUNNING
                ] /
                    this.scanningMetrics.total) *
                    this.totalWidth +
                'px'
            );
        }
        return '0';
    }

    abortWidth() {
        if (
            this.scanningMetrics &&
            this.scanningMetrics.metrics &&
            this.scanningMetrics.total &&
            this.scanningMetrics.metrics[VULNERABILITY_SCAN_STATUS.STOPPED]
        ) {
            return (
                (this.scanningMetrics.metrics[
                    VULNERABILITY_SCAN_STATUS.STOPPED
                ] /
                    this.scanningMetrics.total) *
                    this.totalWidth +
                'px'
            );
        }
        return '0';
    }

    scanNow(): void {
        if (this.onSubmitting) {
            return; // Aoid duplicated submitting
        }

        if (!this.scanAvailable) {
            return; // Aoid page hacking
        }

        this.onSubmitting = true;
        this.scanningService
            .manualScan()
            .pipe(finalize(() => (this.onSubmitting = false)))
            .subscribe(
                () => {
                    this.translate
                        .get('CONFIG.SCANNING.TRIGGER_SCAN_ALL_SUCCESS')
                        .subscribe((res: string) => {
                            this.errorHandlerEntity.info(res);
                        });
                    // reset metrics and then get new metrics
                    this.scanningMetrics = null;
                    this.getMetrics();
                },
                error => {
                    if (error && error.status && error.status === 412) {
                        this.translate
                            .get('CONFIG.SCANNING.TRIGGER_SCAN_ALL_FAIL', {
                                error: '' + errorHandler(error),
                            })
                            .subscribe((res: string) => {
                                this.errorHandlerEntity.error(res);
                            });
                    } else {
                        this.errorHandlerEntity.error(error);
                    }
                }
            );
    }

    reset(cron): void {
        this.schedule = {
            schedule: {
                type: this.CronScheduleComponent.scheduleType,
                cron: cron,
            },
        };
    }

    saveSchedule(cron: string): void {
        let schedule = this.schedule;
        if (
            schedule &&
            schedule.schedule &&
            schedule.schedule.type !== SCHEDULE_TYPE_NONE
        ) {
            this.scanningService
                .putSchedule(this.CronScheduleComponent.scheduleType, cron)
                .subscribe(
                    response => {
                        this.translate
                            .get('CONFIG.SAVE_SUCCESS')
                            .subscribe(res => {
                                this.errorHandlerEntity.info(res);
                                this.CronScheduleComponent.resetSchedule();
                            });
                        this.reset(cron);
                    },
                    error => {
                        this.errorHandlerEntity.error(error);
                    }
                );
        } else {
            this.scanningService
                .postSchedule(this.CronScheduleComponent.scheduleType, cron)
                .subscribe(
                    response => {
                        this.translate
                            .get('CONFIG.SAVE_SUCCESS')
                            .subscribe(res => {
                                this.errorHandlerEntity.info(res);
                                this.CronScheduleComponent.resetSchedule();
                            });
                        this.reset(cron);
                    },
                    error => {
                        this.errorHandlerEntity.error(error);
                    }
                );
        }
    }

    isError(status: string): boolean {
        return status === VULNERABILITY_SCAN_STATUS.ERROR;
    }

    isFinished(status: string): boolean {
        return status === VULNERABILITY_SCAN_STATUS.SUCCESS;
    }

    isInProgress(status: string): boolean {
        return status === VULNERABILITY_SCAN_STATUS.RUNNING;
    }

    isAborted(status: string): boolean {
        return status === VULNERABILITY_SCAN_STATUS.STOPPED;
    }

    isManual() {
        return (
            this.scanningMetrics &&
            this.scanningMetrics.trigger === Triggers.MANUAL
        );
    }

    isSchedule() {
        return (
            this.scanningMetrics &&
            this.scanningMetrics.trigger === Triggers.SCHEDULE
        );
    }

    scanOrStop() {
        if (this.isOnScanning()) {
            this.stopNow();
        } else {
            this.scanNow();
        }
    }

    stopNow() {
        this.onSubmitting = true;
        this.newScanAllService
            .stopScanAll()
            .pipe(finalize(() => (this.onSubmitting = false)))
            .subscribe(
                res => {
                    this.errorHandlerEntity.info(
                        'CONFIG.SCANNING.STOP_SCAN_ALL_SUCCESS'
                    );
                },
                error => {
                    this.errorHandlerEntity.error(error);
                }
            );
    }
}
