import { Injectable } from '@angular/core';
import { EntitiesService, QueryParam, Toaster } from '@iqser/common-ui';
import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain';
import { catchError, switchMap, take, tap } from 'rxjs/operators';
import { firstValueFrom, forkJoin, map, Observable, of } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { PermissionsService } from '../permissions.service';
import { ActiveDossiersService } from '../dossiers/active-dossiers.service';
import { UserService } from '@users/user.service';
import { flatMap } from 'lodash-es';
import { DossierStatsService } from '../dossiers/dossier-stats.service';
import { FilesService } from '../files/files.service';
import { SystemPreferencesService } from '@services/system-preferences.service';
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
import { List, mapEach } from '@iqser/common-ui/lib/utils';

@Injectable({
    providedIn: 'root',
})
export class TrashService extends EntitiesService<TrashItem, TrashItem> {
    constructor(
        private readonly _toaster: Toaster,
        private readonly _systemPreferencesService: SystemPreferencesService,
        private readonly _permissionsService: PermissionsService,
        private readonly _activeDossiersService: ActiveDossiersService,
        private readonly _archivedDossiersService: ArchivedDossiersService,
        private readonly _userService: UserService,
        private readonly _dossierStatsService: DossierStatsService,
        private readonly _filesService: FilesService,
    ) {
        super();
    }

    deleteDossier(dossier: Dossier) {
        const showToast = () => {
            this._toaster.error(_('dossier-listing.delete.delete-failed'), { params: { dossierName: dossier.dossierName } });
            return of(undefined);
        };

        const reloadDossiers$ = dossier.isActive ? this._activeDossiersService.loadAll() : this._archivedDossiersService.loadAll();

        const request$ = this.delete(dossier.id, 'dossier').pipe(
            switchMap(() => reloadDossiers$),
            catchError(showToast),
        );

        return firstValueFrom(request$);
    }

    restore(items: TrashItem[]): Observable<unknown> {
        return this.#executeAction(
            items,
            (dossierIds: string[]) => this._restoreDossiers(dossierIds),
            (dossierId: string, fileIds: string[]) => this._restoreFiles(dossierId, fileIds),
        ).pipe(tap(() => items.forEach(item => this.remove(item.id))));
    }

    hardDelete(items: TrashItem[]): Observable<unknown> {
        return this.#executeAction(
            items,
            (dossierIds: string[]) => this._hardDeleteDossiers(dossierIds),
            (dossierId: string, fileIds: string[]) => this._hardDeleteFiles(dossierId, fileIds),
        ).pipe(tap(() => items.forEach(item => this.remove(item.id))));
    }

    getDossiers(): Observable<TrashDossier[]> {
        return this.getAll<IDossier[]>('deleted-dossiers').pipe(
            mapEach(
                dossier =>
                    new TrashDossier(
                        dossier,
                        this._systemPreferencesService.values.softDeleteCleanupTime,
                        this._permissionsService.canRestoreDossier(dossier),
                        this._permissionsService.canDeleteDossier(dossier),
                        this._userService.getName(dossier.ownerId),
                    ),
            ),
            switchMap(dossiers => this._dossierStatsService.getFor(dossiers.map(d => d.id)).pipe(map(() => dossiers))),
        );
    }

    getFiles(dossierIds = this._activeDossiersService.all.map(d => d.id)): Observable<TrashFile[]> {
        return this._post<Record<string, IFile[]>>(dossierIds, 'status/softdeleted').pipe(
            map(res => flatMap(Object.values(res))),
            mapEach(file => new File(file, this._userService.getName(file.assignee))),
            mapEach(file => {
                const dossier = this._activeDossiersService.find(file.dossierId);
                return new TrashFile(
                    file,
                    dossier.dossierTemplateId,
                    this._systemPreferencesService.values.softDeleteCleanupTime,
                    this._permissionsService.canRestoreFile(file, dossier),
                    this._permissionsService.canHardDeleteFile(file, dossier),
                    this._userService.getName(file.assignee),
                    dossier.dossierName,
                );
            }),
        );
    }

    loadAll(): Observable<TrashItem[]> {
        return forkJoin([this.getDossiers(), this.getFiles()]).pipe(
            map(result => flatMap<TrashItem>(result)),
            tap(items => this.setEntities(items)),
        );
    }

    private _restoreDossiers(dossierIds: string[]): Observable<unknown> {
        return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll()));
    }

    private _hardDeleteDossiers(dossierIds: string[]): Observable<unknown> {
        const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
        return this.delete(body, 'deleted-dossiers/hard-delete', body);
    }

    private _hardDeleteFiles(dossierId: string, fileIds: List) {
        const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
        return super
            .delete({}, `delete/hard-delete/${dossierId}`, queryParams)
            .pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
    }

    private _restoreFiles(dossierId: string, fileIds: List) {
        return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
    }

    #executeAction(items: TrashItem[], dossiersFn: (...args) => Observable<unknown>, filesFn: (...args) => Observable<unknown>) {
        const requests$ = [];

        const dossierIds = items.filter(i => i.isDossier).map(i => i.id);
        if (dossierIds.length) {
            requests$.push(dossiersFn(dossierIds));
        }

        const files = items.filter(i => i.isFile) as TrashFile[];
        const dossierId: string = files.length ? files[0].dossierId : '';
        const fileIds = files.map(i => i.id);
        if (files.length) {
            requests$.push(filesFn(dossierId, fileIds));
        }

        return forkJoin(requests$.map(r => r.pipe(take(1))));
    }
}
