// 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,
    EventEmitter,
    Input,
    OnDestroy,
    OnInit,
    Output,
} from '@angular/core';
import { Subscription, timer } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { ScannerVo } from '../../../../../shared/services';
import { ErrorHandler } from '../../../../../shared/units/error-handler';
import {
    clone,
    CURRENT_BASE_HREF,
    dbEncodeURIComponent,
    DEFAULT_SUPPORTED_MIME_TYPES,
    VULNERABILITY_SCAN_STATUS,
} from '../../../../../shared/units/utils';
import { ArtifactService } from '../../../../../../../ng-swagger-gen/services/artifact.service';
import { Artifact } from '../../../../../../../ng-swagger-gen/models/artifact';
import { NativeReportSummary } from '../../../../../../../ng-swagger-gen/models/native-report-summary';
import {
    EventService,
    HarborEvent,
} from '../../../../../services/event-service/event.service';
import { ScanService } from '../../../../../../../ng-swagger-gen/services/scan.service';
import { ScanType } from 'ng-swagger-gen/models';
import { ScanTypes } from '../../../../../shared/entities/shared.const';
import {
    SkipSessionRenewalService,
    skipSessionRenewal,
} from '../../../../../services/skip-session-renewal.service';

const STATE_CHECK_INTERVAL: number = 3000; // 3s
const RETRY_TIMES: number = 3;

@Component({
    selector: 'hbr-vulnerability-bar',
    templateUrl: './result-bar-chart-component.html',
    styleUrls: ['./scanning.scss'],
})
export class ResultBarChartComponent implements OnInit, OnDestroy {
    @Input() inputScanner: ScannerVo;
    @Input() repoName: string = '';
    @Input() projectName: string = '';
    @Input() artifactDigest: string = '';
    @Input() summary: NativeReportSummary;
    onSubmitting: boolean = false;
    onStopping: boolean = false;
    retryCounter: number = 0;
    stateCheckTimer: Subscription;
    scanSubscription: Subscription;
    stopSubscription: Subscription;
    timerHandler: any;
    @Output()
    submitFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
    // if sending stop scan request is finished, emit to farther component
    @Output()
    submitStopFinish: EventEmitter<boolean> = new EventEmitter<boolean>();
    @Output()
    scanFinished: EventEmitter<Artifact> = new EventEmitter<Artifact>();

    constructor(
        private artifactService: ArtifactService,
        private scanService: ScanService,
        private errorHandler: ErrorHandler,
        private eventService: EventService,
        private skipSessionRenewalService: SkipSessionRenewalService
    ) {}

    ngOnInit(): void {
        if (
            (this.status === VULNERABILITY_SCAN_STATUS.RUNNING ||
                this.status === VULNERABILITY_SCAN_STATUS.PENDING) &&
            !this.stateCheckTimer
        ) {
            // Avoid duplicated subscribing
            this.stateCheckTimer = timer(0, STATE_CHECK_INTERVAL).subscribe(
                () => {
                    this.getSummary();
                }
            );
        }
        if (!this.scanSubscription) {
            this.scanSubscription = this.eventService.subscribe(
                HarborEvent.START_SCAN_ARTIFACT,
                (artifactDigest: string) => {
                    let myFullTag: string =
                        this.repoName + '/' + this.artifactDigest;
                    if (myFullTag === artifactDigest) {
                        this.scanNow();
                    }
                }
            );
        }
        if (!this.stopSubscription) {
            this.stopSubscription = this.eventService.subscribe(
                HarborEvent.STOP_SCAN_ARTIFACT,
                (artifactDigest: string) => {
                    let myFullTag: string =
                        this.repoName + '/' + this.artifactDigest;
                    if (myFullTag === artifactDigest) {
                        this.stopScan();
                    }
                }
            );
        }
    }

    ngOnDestroy(): void {
        if (this.stateCheckTimer) {
            this.stateCheckTimer.unsubscribe();
            this.stateCheckTimer = null;
        }
        if (this.scanSubscription) {
            this.scanSubscription.unsubscribe();
            this.scanSubscription = null;
        }
        if (this.stopSubscription) {
            this.stopSubscription.unsubscribe();
            this.stopSubscription = null;
        }
    }

    // Get vulnerability scanning status
    public get status(): string {
        if (this.summary && this.summary.scan_status) {
            return this.summary.scan_status;
        }
        return VULNERABILITY_SCAN_STATUS.NOT_SCANNED;
    }

    public get completed(): boolean {
        return this.status === VULNERABILITY_SCAN_STATUS.SUCCESS;
    }

    public get error(): boolean {
        return this.status === VULNERABILITY_SCAN_STATUS.ERROR;
    }

    public get queued(): boolean {
        return this.status === VULNERABILITY_SCAN_STATUS.PENDING;
    }

    public get scanning(): boolean {
        return this.status === VULNERABILITY_SCAN_STATUS.RUNNING;
    }
    public get stopped(): boolean {
        return this.status === VULNERABILITY_SCAN_STATUS.STOPPED;
    }
    public get otherStatus(): boolean {
        return !(
            this.completed ||
            this.error ||
            this.queued ||
            this.scanning ||
            this.stopped
        );
    }

    scanNow(): void {
        if (this.onSubmitting) {
            // Avoid duplicated submitting
            console.error('duplicated submit');
            return;
        }

        if (!this.repoName || !this.artifactDigest) {
            console.error('bad repository or tag');
            return;
        }

        this.onSubmitting = true;

        this.scanService
            .scanArtifact({
                projectName: this.projectName,
                reference: this.artifactDigest,
                repositoryName: dbEncodeURIComponent(this.repoName),
                scanType: <ScanType>{
                    scan_type: ScanTypes.VULNERABILITY,
                },
            })
            .pipe(finalize(() => this.submitFinish.emit(false)))
            .subscribe(
                () => {
                    this.onSubmitting = false;
                    // Forcely change status to queued after successful submitting
                    this.summary = {
                        scan_status: VULNERABILITY_SCAN_STATUS.PENDING,
                    };
                    // Start check status util the job is done
                    if (!this.stateCheckTimer) {
                        // Avoid duplicated subscribing
                        this.stateCheckTimer = timer(
                            STATE_CHECK_INTERVAL,
                            STATE_CHECK_INTERVAL
                        ).subscribe(() => {
                            this.getSummary();
                        });
                    }
                },
                error => {
                    this.onSubmitting = false;
                    if (error && error.error && error.error.code === 409) {
                        console.error(error.error.message);
                    } else {
                        this.errorHandler.error(error);
                    }
                }
            );
    }

    getSummary(): void {
        if (!this.repoName || !this.artifactDigest) {
            return;
        }
        this.artifactService
            .getArtifact({
                projectName: this.projectName,
                repositoryName: dbEncodeURIComponent(this.repoName),
                reference: this.artifactDigest,
                withScanOverview: true,
                withAccessory: true,
                XAcceptVulnerabilities: DEFAULT_SUPPORTED_MIME_TYPES,
            })
            .pipe(skipSessionRenewal(this.skipSessionRenewalService))
            .subscribe(
                (artifact: Artifact) => {
                    // To keep the same summary reference, use value copy.
                    if (artifact.scan_overview) {
                        this.copyValue(
                            Object.values(artifact.scan_overview)[0]
                        );
                    }
                    if (!this.queued && !this.scanning) {
                        // Scanning should be done
                        if (this.stateCheckTimer) {
                            this.stateCheckTimer.unsubscribe();
                            this.stateCheckTimer = null;
                        }
                        this.scanFinished.emit(artifact);
                    }
                    this.eventService.publish(
                        HarborEvent.UPDATE_VULNERABILITY_INFO,
                        artifact
                    );
                },
                error => {
                    this.errorHandler.error(error);
                    this.retryCounter++;
                    if (this.retryCounter >= RETRY_TIMES) {
                        // Stop timer
                        if (this.stateCheckTimer) {
                            this.stateCheckTimer.unsubscribe();
                            this.stateCheckTimer = null;
                        }
                        this.retryCounter = 0;
                    }
                }
            );
    }

    copyValue(newVal: NativeReportSummary): void {
        if (!this.summary || !newVal || !newVal.scan_status) {
            return;
        }
        this.summary = clone(newVal);
    }
    viewLog(): string {
        return `${CURRENT_BASE_HREF}/projects/${
            this.projectName
        }/repositories/${dbEncodeURIComponent(this.repoName)}/artifacts/${
            this.artifactDigest
        }/scan/${this.summary.report_id}/log`;
    }
    getScanner(): ScannerVo {
        if (this.summary && this.summary.scanner) {
            return this.summary.scanner;
        }
        return this.inputScanner;
    }
    stopScan() {
        if (this.onStopping) {
            // Avoid duplicated stopping command
            console.error('duplicated stopping command');
            return;
        }
        if (!this.repoName || !this.artifactDigest) {
            console.error('bad repository or artifact');
            return;
        }
        this.onStopping = true;

        this.scanService
            .stopScanArtifact({
                projectName: this.projectName,
                reference: this.artifactDigest,
                repositoryName: dbEncodeURIComponent(this.repoName),
                scanType: <ScanType>{
                    scan_type: ScanTypes.VULNERABILITY,
                },
            })
            .pipe(
                finalize(() => {
                    this.submitStopFinish.emit(false);
                    this.onStopping = false;
                })
            )
            .subscribe(
                () => {
                    // Start check status util the job is done
                    if (!this.stateCheckTimer) {
                        // Avoid duplicated subscribing
                        this.stateCheckTimer = timer(
                            STATE_CHECK_INTERVAL,
                            STATE_CHECK_INTERVAL
                        ).subscribe(() => {
                            this.getSummary();
                        });
                    }
                    this.errorHandler.info(
                        'VULNERABILITY.TRIGGER_STOP_SUCCESS'
                    );
                },
                error => {
                    this.errorHandler.error(error);
                }
            );
    }
}
