
import { throwError as observableThrowError, Observable, timer, of } from 'rxjs';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Router } from '@angular/router';
// import { Response } from '@angular/http';
import { AppState } from 'app/app.state';
import { Store } from '@ngrx/store';
import { UpdateProgress } from '@core/actions/progress.actions';

import { ResourceFetcher } from '../services';
import { FetchHypermedia, HypermediaActionTypes, ReceivedHypermedia } from '../actions';
import { Hypermedia } from '../interfaces';
import { Injectable } from '@angular/core';
import { mapTo, flatMap, finalize, retryWhen, catchError, map, take } from 'rxjs/operators';
import { HttpResponse, HttpErrorResponse } from '@angular/common/http';

@Injectable()
export class HypermediaEffects {
    private readonly statusCodeMessages = {
        0: 'disconnected',
        400: 'badRequest',
        404: 'notFound',
        403: 'forbidden',
        409: 'conflict',
        503: 'serviceUnavailable',
        500: 'internalServerError'
    };

    constructor(private actions: Actions,
        private store: Store<AppState>,
        private hypermedia: ResourceFetcher,
        private router: Router) { }

    @Effect()
    fetchHypermedia = this.actions
        .pipe(
            ofType(HypermediaActionTypes.FETCH_HYPERMEDIA),
            flatMap(({ payload }: FetchHypermedia) =>
                of(0).pipe(// new observable to be retried

                    flatMap(_ =>
                        this.hypermedia
                            .get(payload.path, payload.scope, payload.authorize, payload.tokenOverride)
                    ),
                    finalize(() => this.store.dispatch(new UpdateProgress(undefined))),
                    retryWhen(errors => errors.pipe(
                        flatMap(error => {
                            if (error.status === 401) { // retry on 401 status code, after 1000 seconds
                                return timer(1000).pipe(mapTo(error));
                            }

                            return observableThrowError(error);
                        }),
                        take(3) // 3 times
                    )),
                    catchError(error => {
                        return this.applyInterceptors(payload.interceptors, error)
                    }),
                    catchError(error => {
                        return this.handleResourceErrors(error);
                    }),
                    map(hypermedia => new ReceivedHypermedia({
                        path: payload.path,
                        scope: payload.scope,
                        authorize: payload.authorize,
                        hypermedia: { ...hypermedia, source: { ...payload, receivedAt: Date.now() } }
                    })))
            )
        )

    private handleResourceErrors(response: HttpErrorResponse): Array<Hypermedia> {
        if (!(response instanceof HttpErrorResponse)) {
            return [];
        }


        if (response.status in this.statusCodeMessages) {
            if (response.error && response.error.class && response.error.class.includes('not-redirect')) { 
                return [response.error];
            }
            this.router.navigate(['error', this.statusCodeMessages[response.status]]);
        }

        return [response.error];
    }

    applyInterceptors(interceptors: any, httpErrorResponse: HttpErrorResponse): Array<any> {
        if (interceptors[httpErrorResponse.status]) {
            interceptors[httpErrorResponse.status](httpErrorResponse.error);

            return [];
        }

        throw httpErrorResponse;
    }
}
