import { UpdateProfile } from './../model/update-profile.model';
import { InfoDialogComponent } from './../../core/info-dialog/info-dialog.component';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { ApiResponse } from './../../model/apiResponse';
import { AuthState } from 'src/app/auth/store/auth.reducers';
import { AppState } from './../../store/app.reducers';
import { HttpErrorBody } from './../../model/httpErrorBody';
import { MatSnackBar } from '@angular/material/snack-bar';
import { LoginResponse } from '../../model/loginResponse';
import { Injectable } from "@angular/core";
import { Actions, ofType, Effect } from "@ngrx/effects";
import { map, switchMap, finalize, catchError, tap, mergeMap, withLatestFrom } from "rxjs/operators";
import { REQUEST_TOKEN, RequestTokenAction, SetTokenAction, SetUserAction, SET_TOKEN, SIGN_OFF, SignOffAction, CloseLoginDialogAction, SET_USER, REQUEST_REGISTRATION, RequestRegistrationAction, REQUEST_USERS, SetUsersAction, REQUEST_DELETE_USER, RequestDeleteUserAction, REQUEST_DELETE_OTHER_USER, RequestDeleteOtherUserAction, REQUEST_UPDATE_OTHER_USER, RequestUpdateOtherUserAction, RequestUpdateUserAction, REQUEST_UPDATE_USER, REQUEST_APPROVE_USERS, RequestApproveUsersAction, REQUEST_REJECT_USERS, RequestRejectUsersAction, InitLoginDialogAction, SetLoginTimeoutAction, RemoveLoginTimeoutAction, SimpleSignOffAction } from "./auth.actions";
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Login } from "../model/login.model";
import { CookieService } from "ngx-cookie-service";
import { LoadingService } from "src/app/core/loading.service";
import { of, empty } from 'rxjs';
import { Router } from '@angular/router';
import { User } from '../../core/model/user.model';
import { Store } from '@ngrx/store';
import { RegisterUser } from '../model/register-user.model';
import { ConfirmDialogData } from 'src/app/core/confirm-dialog/confirm-dialog.component';
import { UpdateOtherProfile } from '../model/update-other-profile.model';
import { LoginDialogComponent } from '../login/login-dialog/login-dialog.component';
import { JwtBody } from 'src/app/model/jwtBody';
import { JwtUtil } from 'src/app/shared/jwt.util';

@Injectable()
export class AuthEffects{

    @Effect()
    requestToken = this.actions.pipe(
        ofType(REQUEST_TOKEN),
        map(
            (action: RequestTokenAction) => {
                return action.payload;
            }
        ),
        switchMap(
            (credentials: Login) => {
                this.loadingService.enableLoading();
                return this.http.post('/tadis/auth/login', credentials).pipe(
                    withLatestFrom(this.appStore.select('authState')),
                    mergeMap(
                        ([response, state]: [LoginResponse, AuthState]) => {
                            let token =  response.token;
                            if (token.length){
                                this.cookieService.set('tadis', token, null, "/");
                                if (state.loginDialogReference !== null){
                                    state.loginDialogReference.close(true);
                                    return [new SetTokenAction(token), new CloseLoginDialogAction()];
                                }
                                else {
                                    this.router.navigate(['/']);
                                    return [new SetTokenAction(token)];
                                }
                            }
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => {
                            let error: HttpErrorBody = errorResponse.error;
                            if (errorResponse.status === 401 && error.error === "Invalid Credentials"){
                                this.snackBar.open("Špatné jméno či heslo.", 'zavřít', {
                                    duration: 2500
                                });
                            }
                            if (errorResponse.status === 401 && error.error === "Inactive User"){
                                this.snackBar.open("Účet je neaktivní. Vyčkejte na jeho aktivaci správcem.", 'zavřít', {
                                    duration: 3500
                                });
                            }
                            return of(new SimpleSignOffAction());
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); }),
                );
                
            }
        )
    );

    @Effect({ dispatch: false })
    signOff = this.actions.pipe(
        ofType(SIGN_OFF),
        tap(
            () => {
                this.cookieService.delete('tadis');
            }
        )
    );

    @Effect()
    requestUser = this.actions.pipe(
        ofType(SET_TOKEN),
        switchMap(
            (action: SetTokenAction) => {
                let body: JwtBody = JwtUtil.getJwtBodyFromToken(action.payload);
                if (body !== null){
                    let tokenExp: Date = new Date(body.exp * 1000);                
                    let expiresIn: number = Math.floor((tokenExp.getTime() - new Date().getTime())/5)*4;
                    let loginTimeout: any = setTimeout(() => {
                        let dialogRef: any = this.initLoginDialog();
                        this.appStore.dispatch(new InitLoginDialogAction(dialogRef));
                        this.appStore.dispatch(new RemoveLoginTimeoutAction());
                    }, expiresIn);
                    this.appStore.dispatch(new SetLoginTimeoutAction(loginTimeout));
                }
                return this.http.get<User>('/tadis/users/profile').pipe(
                    map(
                        (user: User) => {
                            return new SetUserAction(user);
                        }
                    ),
                    catchError(
                        () => { return of(new SimpleSignOffAction()); }
                    )
                );
            }
        )
    );

    @Effect({ dispatch: false })
    requestRegistration = this.actions.pipe(
        ofType(REQUEST_REGISTRATION),
        switchMap(
            (action: RequestRegistrationAction) => {
                let f = action.registrationForm.value;
                let newUser: RegisterUser = new RegisterUser(f.email, f.firstName, f.lastName, f.company, f.position, f.password);
                this.loadingService.enableLoading();
                return this.http.post('/tadis/auth/register', newUser).pipe(
                    map(
                        (response: ApiResponse) => {
                            this.snackBar.open('Uživatel úspěšně vytvořen.', 'zavřít',{
                                duration: 3000
                            });
                            this.router.navigate(['/login']);
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => { 
                            let error: HttpErrorBody = errorResponse.error;
                            if (error.status === 409){
                                action.registrationForm.controls['email'].setErrors({ userExists: true });
                                this.snackBar.open('Uživatel s tímto emailem již existuje', 'zavřít',{
                                    duration: 3000
                                });
                            }
                            return empty();
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )

    );

    @Effect()
    requestUsers = this.actions.pipe(
        ofType(REQUEST_USERS),
        switchMap(
            () => { 
                this.loadingService.enableLoading();
                return this.http.get('/tadis/users/profiles').pipe(
                    map(
                        (users: User[]) => {
                            return new SetUsersAction(users);
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                )
            } 
        )
    );

    @Effect()
    deleteUser = this.actions.pipe(
        ofType(REQUEST_DELETE_USER),
        switchMap(
            (action: RequestDeleteUserAction) => {
                this.loadingService.enableLoading();
                return this.http.delete('/tadis/users/delete').pipe( 
                    map(
                        (response: ApiResponse) => {
                            this.snackBar.open('Profil úspěšně smazán.', 'zavřít', { duration: 3000 });
                            this.router.navigate(['/']);
                            return new SignOffAction();
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => {
                            if (errorResponse.status === 409){
                                let dialogData: ConfirmDialogData = {
                                    title: 'Nelze smazat!',
                                    text: 'Tento profil nemohl být smazán, jelikož se jedná o jediný existující Správcovský profil.'
                                }
                                this.dialog.open(InfoDialogComponent, {
                                    maxWidth: '300px',
                                    data: dialogData
                                });
                            }
                            return empty();
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );

    @Effect()
    deleteOtherUser = this.actions.pipe(
        ofType(REQUEST_DELETE_OTHER_USER),
        switchMap(
            (action: RequestDeleteOtherUserAction) => {
                let id: number = action.id;
                this.loadingService.enableLoading();
                return this.http.delete('/tadis/users/delete/' + id).pipe(
                    map(
                        (users: User[]) => {
                            this.snackBar.open('Profil úspěšně smazán.', 'zavřit', { duration: 3000 });
                            if (this.router.url !== '/profiles')
                                this.router.navigate(['/profiles'])
                            return new SetUsersAction(users);
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => { return empty(); }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );

    @Effect({ dispatch: false })
    updateUser = this.actions.pipe(
        ofType(REQUEST_UPDATE_USER),
        switchMap(
            (action: RequestUpdateUserAction) => {
                this.loadingService.enableLoading();
                let f = action.profileForm.value;
                let profileToUpdate: UpdateProfile = new UpdateProfile(f.email, f.firstName, f.lastName, f.company, f.position, f.oldPassword, f.newPassword);
                return this.http.put('/tadis/users/update', profileToUpdate).pipe(
                    map(
                        (user: User) => {
                            this.snackBar.open('Profil úspěšně aktualizován', 'zavřít',{ duration: 3000 });
                            return new SetUserAction(user);
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => { 
                            let error: HttpErrorBody = errorResponse.error;
                            if (errorResponse.status === 401 && error.error === "Wrong Password"){
                                action.profileForm.controls['oldPassword'].setErrors({ wrongPassword: true });
                                this.snackBar.open('Špatné současné heslo', 'zavřít',{ duration: 3000 });
                            }
                            if (errorResponse.status === 409){
                                action.profileForm.controls['email'].setErrors({ userExists: true });
                                this.snackBar.open('Profil s tímto emailem již existuje', 'zavřít',{ duration: 3000 });
                            }
                            return empty(); 
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );

    @Effect()
    updateOtherUser = this.actions.pipe(
        ofType(REQUEST_UPDATE_OTHER_USER),
        switchMap(
            (action: RequestUpdateOtherUserAction) => {
                this.loadingService.enableLoading();
                let f = action.profileForm.value;
                let profileToUpdate: UpdateOtherProfile = new UpdateOtherProfile(f.id, f.email, f.firstName, f.lastName, f.company, f.position, f.role, f.newPassword);
                return this.http.put('/tadis/users/update/' + profileToUpdate.id, profileToUpdate).pipe(
                    map(
                        (users: User[]) => {
                            this.snackBar.open('Profil úspěšně aktualizován.', 'zavřít',{ duration: 3000 });
                            this.router.navigate(['/profiles'])
                            return new SetUsersAction(users);
                        }
                    ),
                    catchError(
                        (errorResponse: HttpErrorResponse) => { 
                            if (errorResponse.status == 409){
                                action.profileForm.controls['email'].setErrors({ userExists: true });
                                this.snackBar.open('Profil s tímto emailem již existuje', 'zavřít',{ duration: 3000 });
                            }
                            return empty(); 
                        }
                    ),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );


    @Effect()
    approveUsers = this.actions.pipe(
        ofType(REQUEST_APPROVE_USERS),
        switchMap(
            (action: RequestApproveUsersAction) => {
                return this.http.put('/tadis/users/approve/', action.users).pipe(
                    map(
                        (users: User[]) => {
                            this.snackBar.open('Úspěšně schváleno', 'zavřít',{ duration: 3000 });
                            return new SetUsersAction(users);
                        }
                    ),
                    catchError((errorResponse: HttpErrorResponse) => { return empty(); }),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );

    @Effect()
    rejectUsers = this.actions.pipe(
        ofType(REQUEST_REJECT_USERS),
        switchMap(
            (action: RequestRejectUsersAction) => {
                return this.http.put('/tadis/users/reject/', action.users).pipe(
                    map(
                        (users: User[]) => {
                            this.snackBar.open('Úspěšně zamítnuto', 'zavřít',{ duration: 3000 });
                            return new SetUsersAction(users);
                        }
                    ),
                    catchError((errorResponse: HttpErrorResponse) => { return empty(); }),
                    finalize(() => { this.loadingService.disableLoading(); })
                );
            }
        )
    );

    constructor(
        private actions: Actions, 
        private http: HttpClient, 
        private cookieService: CookieService, 
        private loadingService: LoadingService,
        private snackBar: MatSnackBar,
        private dialog: MatDialog,
        private router: Router,
        private appStore: Store<AppState>
    ){}

    private initLoginDialog(): MatDialogRef<LoginDialogComponent>{
        let dialogRef: MatDialogRef<LoginDialogComponent> = this.dialog.open(LoginDialogComponent, {
            disableClose: true
        });
        return dialogRef;
    }
}