// 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, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { Subscription, Observable, forkJoin, of } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
import { SessionService } from '../../../shared/services/session.service';
import { AppConfigService } from '../../../services/app-config.service';
import { NewUserModalComponent } from './new-user-modal.component';
import { UserService } from './user.service';
import { User } from './user';
import { ChangePasswordComponent } from './change-password/change-password.component';
import { map, catchError } from 'rxjs/operators';
import { OperationService } from '../../../shared/components/operation/operation.service';
import {
    operateChanges,
    OperateInfo,
    OperationState,
} from '../../../shared/components/operation/operate';
import { ConfirmationDialogService } from '../../global-confirmation-dialog/confirmation-dialog.service';
import {
    CONFIG_AUTH_MODE,
    ConfirmationButtons,
    ConfirmationState,
    ConfirmationTargets,
    PAGE_SIZE_OPTIONS,
} from '../../../shared/entities/shared.const';
import { errorHandler } from '../../../shared/units/shared.utils';
import { ConfirmationMessage } from '../../global-confirmation-dialog/confirmation-message';
import { HttpErrorResponse } from '@angular/common/http';
import {
    getPageSizeFromLocalStorage,
    PageSizeMapKeys,
    setPageSizeToLocalStorage,
} from '../../../shared/units/utils';

/**
 * NOTES:
 *   Pagination for this component is a temporary workaround solution. It will be replaced in future release.
 *
 **
 * class UserComponent
 * @implements {OnInit}
 * @implements {OnDestroy}
 */

@Component({
    selector: 'harbor-user',
    templateUrl: 'user.component.html',
    styleUrls: ['user.component.scss'],
    providers: [UserService],
})
export class UserComponent implements OnDestroy {
    clrPageSizeOptions: number[] = PAGE_SIZE_OPTIONS;
    users: User[] = [];
    selectedRow: User[] = [];
    ISADMINISTRATOR: string = 'USER.ENABLE_ADMIN_ACTION';

    currentTerm: string;
    totalCount: number = 0;
    currentPage: number = 1;
    pageSize: number = getPageSizeFromLocalStorage(
        PageSizeMapKeys.SYSTEM_USER_COMPONENT
    );
    timerHandler: any;

    private onGoing: boolean = true;
    private adminMenuText: string = '';
    private adminColumn: string = '';
    private deletionSubscription: Subscription;

    @ViewChild(NewUserModalComponent, { static: true })
    newUserDialog: NewUserModalComponent;
    @ViewChild(ChangePasswordComponent, { static: true })
    changePwdDialog: ChangePasswordComponent;

    constructor(
        private userService: UserService,
        private translate: TranslateService,
        private deletionDialogService: ConfirmationDialogService,
        private msgHandler: MessageHandlerService,
        private session: SessionService,
        private appConfigService: AppConfigService,
        private operationService: OperationService
    ) {
        this.deletionSubscription =
            deletionDialogService.confirmationConfirm$.subscribe(confirmed => {
                if (
                    confirmed &&
                    confirmed.source === ConfirmationTargets.USER &&
                    confirmed.state === ConfirmationState.CONFIRMED
                ) {
                    this.delUser(confirmed.data);
                }
            });
    }

    isMySelf(uid: number): boolean {
        let currentUser = this.session.getCurrentUser();
        if (currentUser) {
            if (currentUser.user_id === uid) {
                return true;
            }
        }

        return false;
    }

    get onlySelf(): boolean {
        return (
            this.selectedRow.length === 1 &&
            this.isMySelf(this.selectedRow[0].user_id)
        );
    }

    public get canCreateUser(): boolean {
        let appConfig = this.appConfigService.getConfig();
        if (appConfig) {
            return !(
                appConfig.auth_mode === 'ldap_auth' ||
                appConfig.auth_mode === 'uaa_auth' ||
                appConfig.auth_mode === 'oidc_auth'
            );
        } else {
            return true;
        }
    }

    public get ifSameRole(): boolean {
        let usersRole: number[] = [];
        this.selectedRow.forEach(user => {
            if (user.user_id === 0 || this.isMySelf(user.user_id)) {
                return false;
            }
            if (user.sysadmin_flag) {
                usersRole.push(1);
            } else {
                usersRole.push(0);
            }
        });
        if (usersRole.length && usersRole.every(num => num === 0)) {
            this.ISADMINISTRATOR = 'USER.ENABLE_ADMIN_ACTION';
            return true;
        }
        if (usersRole.length && usersRole.every(num => num === 1)) {
            this.ISADMINISTRATOR = 'USER.DISABLE_ADMIN_ACTION';
            return true;
        }
        return false;
    }

    isSystemAdmin(u: User): string {
        if (!u) {
            return '{{MISS}}';
        }
        let key: string = u.sysadmin_flag
            ? 'USER.IS_ADMIN'
            : 'USER.IS_NOT_ADMIN';
        const appConfig = this.appConfigService.getConfig();
        if (
            appConfig &&
            appConfig.auth_mode !== CONFIG_AUTH_MODE.DB_AUTH &&
            !u.sysadmin_flag
        ) {
            key = 'USER.UNKNOWN';
        }
        this.translate
            .get(key)
            .subscribe((res: string) => (this.adminColumn = res));
        return this.adminColumn;
    }

    adminActions(u: User): string {
        if (!u) {
            return '{{MISS}}';
        }
        let key: string = u.sysadmin_flag
            ? 'USER.DISABLE_ADMIN_ACTION'
            : 'USER.ENABLE_ADMIN_ACTION';
        this.translate
            .get(key)
            .subscribe((res: string) => (this.adminMenuText = res));
        return this.adminMenuText;
    }

    public get inProgress(): boolean {
        return this.onGoing;
    }

    ngOnDestroy(): void {
        if (this.deletionSubscription) {
            this.deletionSubscription.unsubscribe();
        }

        if (this.timerHandler) {
            clearInterval(this.timerHandler);
            this.timerHandler = null;
        }
    }

    openChangePwdModal(): void {
        if (this.selectedRow.length === 1) {
            this.changePwdDialog.open(this.selectedRow[0].user_id);
        }
    }

    // Filter items by keywords
    doFilter(terms: string): void {
        this.selectedRow = [];
        this.currentTerm = terms.trim();
        this.currentPage = 1;
        this.onGoing = true;
        this.getUserListByPaging();
    }

    // Disable the admin role for the specified user
    changeAdminRole(): void {
        let observableLists: any[] = [];
        if (this.selectedRow.length) {
            if (this.ISADMINISTRATOR === 'USER.ENABLE_ADMIN_ACTION') {
                for (let i = 0; i < this.selectedRow.length; i++) {
                    // Double confirm user is existing
                    if (
                        this.selectedRow[i].user_id === 0 ||
                        this.isMySelf(this.selectedRow[i].user_id)
                    ) {
                        continue;
                    }
                    let updatedUser: User = new User();
                    updatedUser.user_id = this.selectedRow[i].user_id;

                    updatedUser.sysadmin_flag = true; // Set as admin
                    observableLists.push(
                        this.userService.updateUserRole(updatedUser)
                    );
                }
            }
            if (this.ISADMINISTRATOR === 'USER.DISABLE_ADMIN_ACTION') {
                for (let i = 0; i < this.selectedRow.length; i++) {
                    // Double confirm user is existing
                    if (
                        this.selectedRow[i].user_id === 0 ||
                        this.isMySelf(this.selectedRow[i].user_id)
                    ) {
                        continue;
                    }
                    let updatedUser: User = new User();
                    updatedUser.user_id = this.selectedRow[i].user_id;

                    updatedUser.sysadmin_flag = false; // Set as none admin
                    observableLists.push(
                        this.userService.updateUserRole(updatedUser)
                    );
                }
            }

            forkJoin(...observableLists).subscribe(
                () => {
                    this.selectedRow = [];
                    this.refresh();
                },
                error => {
                    this.selectedRow = [];
                    this.msgHandler.handleError(error);
                }
            );
        }
    }

    // Delete the specified user
    deleteUsers(users: User[]): void {
        let userArr: string[] = [];
        if (this.onlySelf) {
            return;
        }

        if (users && users.length) {
            users.forEach(user => {
                userArr.push(user.username);
            });
        }
        // Confirm deletion
        let msg: ConfirmationMessage = new ConfirmationMessage(
            'USER.DELETION_TITLE',
            'USER.DELETION_SUMMARY',
            userArr.join(','),
            users,
            ConfirmationTargets.USER,
            ConfirmationButtons.DELETE_CANCEL
        );
        this.deletionDialogService.openComfirmDialog(msg);
    }

    delUser(users: User[]): void {
        let observableLists: any[] = [];
        if (users && users.length) {
            users.forEach(user => {
                observableLists.push(this.delOperate(user));
            });

            forkJoin(...observableLists).subscribe(resArr => {
                let error;
                if (resArr && resArr.length) {
                    resArr.forEach(item => {
                        if (item instanceof HttpErrorResponse) {
                            error = errorHandler(item);
                        }
                    });
                }
                if (error) {
                    this.msgHandler.handleError(error);
                } else {
                    this.translate
                        .get('BATCH.DELETED_SUCCESS')
                        .subscribe(res => {
                            this.msgHandler.showSuccess(res);
                        });
                }
                this.selectedRow = [];
                this.currentTerm = '';
                this.refresh();
            });
        }
    }

    delOperate(user: User): Observable<any> {
        // init operation info
        let operMessage = new OperateInfo();
        operMessage.name = 'OPERATION.DELETE_USER';
        operMessage.data.id = user.user_id;
        operMessage.state = OperationState.progressing;
        operMessage.data.name = user.username;
        this.operationService.publishInfo(operMessage);

        if (this.isMySelf(user.user_id)) {
            return this.translate.get('BATCH.DELETED_FAILURE').pipe(
                map(res => {
                    operateChanges(operMessage, OperationState.failure, res);
                })
            );
        }

        return this.userService.deleteUser(user.user_id).pipe(
            map(() => {
                this.translate.get('BATCH.DELETED_SUCCESS').subscribe(res => {
                    operateChanges(operMessage, OperationState.success);
                });
            }),
            catchError(error => {
                const message = errorHandler(error);
                this.translate
                    .get(message)
                    .subscribe(res =>
                        operateChanges(operMessage, OperationState.failure, res)
                    );
                return of(error);
            })
        );
    }

    // Refresh the user list
    refreshUser(): void {
        this.selectedRow = [];
        // Start to get
        this.currentTerm = '';
        this.onGoing = true;
        this.getUserListByPaging();
    }

    // Add new user
    addNewUser(): void {
        if (!this.canCreateUser) {
            return; // No response to this hacking action
        }
        this.newUserDialog.open();
    }

    // Add user to the user list
    addUserToList(user: User): void {
        // Currently we can only add it by reloading all
        this.refresh();
    }

    // Data loading
    load(state: any): void {
        if (state && state.page) {
            this.pageSize = state.page.size;
            setPageSizeToLocalStorage(
                PageSizeMapKeys.SYSTEM_USER_COMPONENT,
                this.pageSize
            );
        }
        this.selectedRow = [];
        this.onGoing = true;
        this.getUserListByPaging();
    }

    refresh(): void {
        this.currentPage = 1; // Refresh pagination
        this.refreshUser();
    }

    getUserListByPaging() {
        this.userService
            .getUserListByPaging(
                this.currentPage,
                this.pageSize,
                this.currentTerm
            )
            .subscribe(
                response => {
                    // Get total count
                    if (response.headers) {
                        let xHeader: string =
                            response.headers.get('X-Total-Count');
                        if (xHeader) {
                            this.totalCount = parseInt(xHeader, 0);
                        }
                    }
                    this.users = response.body as User[];
                    this.onGoing = false;
                },
                error => {
                    this.msgHandler.handleError(error);
                    this.onGoing = false;
                }
            );
    }
}
