import { Injectable, TemplateRef } from '@angular/core';
import {
    ActionConfig,
    getConfig,
    IqserPermissionsService,
    ListingMode,
    ListingModes,
    TableColumnConfig,
    WorkflowColumn,
    WorkflowConfig,
} from '@iqser/common-ui';
import {
    annotationDefaultColorConfig,
    AnnotationShapeMap,
    AppConfig,
    DOSSIER_ID,
    File,
    IFileAttributeConfig,
    ProcessingType,
    StatusSorter,
    User,
    WorkflowFileStatus,
    WorkflowFileStatuses,
} from '@red/domain';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { UserService } from '@users/user.service';
import { DossiersDialogService } from '../shared-dossiers/services/dossiers-dialog.service';
import { annotationFilterChecker, RedactionFilterSorter } from '../../utils';
import { workloadTranslations } from '@translations/workload-translations';
import { BehaviorSubject, Observable } from 'rxjs';
import { BulkActionsService } from './services/bulk-actions.service';
import dayjs from 'dayjs';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { map } from 'rxjs/operators';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { IFilterGroup, INestedFilter, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { getParam, List } from '@iqser/common-ui/lib/utils';

@Injectable()
export class ConfigService {
    readonly #dossierId = getParam(DOSSIER_ID);
    readonly #currentUser = getCurrentUser<User>();
    readonly #config = getConfig<AppConfig>();
    readonly #listingMode$: BehaviorSubject<ListingMode>;
    readonly #isDocumine = getConfig().IS_DOCUMINE;
    readonly listingMode$: Observable<ListingMode>;

    constructor(
        private readonly _permissionsService: PermissionsService,
        private readonly _translateService: TranslateService,
        private readonly _userService: UserService,
        private readonly _dialogService: DossiersDialogService,
        private readonly _bulkActionsService: BulkActionsService,
        private readonly _defaultColorsService: DefaultColorsService,
        private readonly _dictionariesMapService: DictionariesMapService,
        private readonly _userPreferenceService: UserPreferenceService,
        private readonly _dossiersService: DossiersService,
        private readonly _iqserPermissionsService: IqserPermissionsService,
    ) {
        const previousListingMode = this._userPreferenceService.getFilesListingMode();
        const listingMode = previousListingMode ? previousListingMode : ListingModes.table;
        this.#listingMode$ = new BehaviorSubject<ListingMode>(listingMode);
        this.listingMode$ = this.#listingMode$.asObservable();
    }

    get listingMode(): ListingMode {
        return this.#listingMode$.value;
    }

    set listingMode(listingMode: ListingMode) {
        this.#listingMode$.next(listingMode);
        this._userPreferenceService.saveFilesListingMode(listingMode).then();
    }

    get #dossier() {
        return this._dossiersService.find(this.#dossierId);
    }

    workflowConfig(): WorkflowConfig<File, WorkflowFileStatus> {
        const newColumn = this.#getNewColumn();
        const underReviewColumn = this.#getUnderReviewColumn();
        const underApprovalColumn = this.#getUnderApprovalColumn();
        const approvedColumn = this.#getApprovedColumn();

        return {
            columnIdentifierFn: entity => entity.workflowStatus,
            itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
            columns: [newColumn, underReviewColumn, underApprovalColumn, approvedColumn],
        };
    }

    workflowConfigRss(): WorkflowConfig<File, WorkflowFileStatus> {
        const newColumn = this.#getNewColumn();
        const underReviewColumn = this.#getUnderReviewColumn();
        const approvedColumn = this.#getApprovedColumn();

        return {
            columnIdentifierFn: entity => entity.workflowStatus,
            itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
            columns: [newColumn, underReviewColumn, approvedColumn],
        };
    }

    actionConfig(dossierId: string, disabled$: Observable<boolean>): List<ActionConfig> {
        return [
            {
                id: 'editDossier',
                label: this._translateService.instant('dossier-overview.header-actions.edit'),
                action: () => this.#openEditDossierDialog(dossierId),
                icon: 'iqser:edit',
                hide: !this.#currentUser.isManager && !this._iqserPermissionsService.has(Roles.dossiers.edit),
                helpModeKey: 'edit_dossier',
                disabled$,
            },
        ];
    }

    tableConfig(displayedAttributes: IFileAttributeConfig[]): TableColumnConfig<File>[] {
        const dynamicColumns: TableColumnConfig<File>[] = displayedAttributes.map(config => ({
            label: config.label,
            notTranslatable: true,
        }));
        const columns: TableColumnConfig<File>[] = [
            {
                label: _('dossier-overview.table-col-names.name'),
                sortByKey: 'searchKey',
                width: '3fr',
            },
            {
                label: _('dossier-overview.table-col-names.added-on'),
                sortByKey: 'added',
                width: '2fr',
            },
            {
                label: _('dossier-overview.table-col-names.last-modified'),
                sortByKey: 'redactionModificationDate',
                width: '2fr',
            },
            ...dynamicColumns,
            {
                label: _('dossier-overview.table-col-names.assigned-to'),
                class: 'user-column',
                sortByKey: 'reviewerName',
                width: '2fr',
            },
            {
                label: _('dossier-overview.table-col-names.pages'),
                sortByKey: 'numberOfPages',
            },
            {
                label: _('dossier-overview.table-col-names.status'),
                class: 'flex-end',
                sortByKey: 'statusSort',
            },
        ];
        if (!this.#isDocumine) {
            columns.splice(3 + displayedAttributes.length, 0, {
                label: _('dossier-overview.table-col-names.needs-work'),
            });
        }
        return columns;
    }

    filterGroups(
        entities: File[],
        fileAttributeConfigs: IFileAttributeConfig[],
        dossierTemplateId: string,
        needsWorkFilterTemplate: TemplateRef<unknown>,
        checkedRequiredFilters: () => NestedFilter[],
        checkedNotRequiredFilters: () => NestedFilter[],
    ) {
        const allDistinctWorkflowFileStatuses = new Set<WorkflowFileStatus>();
        const allDistinctPeople = new Set<string>();
        const allDistinctNeedsWork = new Set<string>();
        const allDistinctProcessingTypes = new Set<ProcessingType>();

        const dynamicFilters = new Map<string, Set<string>>();

        const filterGroups: IFilterGroup[] = [];

        entities.forEach(file => {
            allDistinctPeople.add(file.assignee);
            allDistinctWorkflowFileStatuses.add(file.workflowStatus);

            if (file.analysisRequired) {
                allDistinctNeedsWork.add('analysis');
            }
            if (file.hintsOnly) {
                allDistinctNeedsWork.add('hint');
            }
            if (file.hasRedactions) {
                allDistinctNeedsWork.add('redaction');
            }
            if (file.hasSuggestions) {
                allDistinctNeedsWork.add('suggestion');
            }
            if (file.hasUpdates && file.assignee === this._userService.currentUser.id && !file.isApproved) {
                allDistinctNeedsWork.add('updated');
            }
            if (file.hasImages) {
                allDistinctNeedsWork.add('image');
            }
            if (file.hasNone) {
                allDistinctNeedsWork.add('none');
            }
            if (file.hasAnnotationComments) {
                allDistinctNeedsWork.add('comment');
            }

            allDistinctProcessingTypes.add(file.processingType);

            // extract values for dynamic filters
            fileAttributeConfigs.forEach(config => {
                if (config.filterable) {
                    const filterKey = `${config.id}:${config.label}`;
                    let filters = dynamicFilters.get(filterKey);
                    if (!filters) {
                        dynamicFilters.set(filterKey, new Set<string>());
                        filters = dynamicFilters.get(filterKey);
                    }
                    const filterValue = file.fileAttributes?.attributeIdToValue[config.id];
                    if (filterValue === undefined || filterValue === null) {
                        filters.add(undefined);
                    } else {
                        filters.add(filterValue);
                    }
                }
            });
        });

        if (this.listingMode === ListingModes.table) {
            const statusFilters = [...allDistinctWorkflowFileStatuses].map(
                status =>
                    new NestedFilter({
                        id: status,
                        label: this._translateService.instant(workflowFileStatusTranslations[status]),
                    }),
            );
            statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]);
            filterGroups.push({
                slug: 'statusFilters',
                label: this._translateService.instant('filters.documents-status'),
                icon: 'red:status',
                filters: statusFilters,
                checker: keyChecker('workflowStatus'),
            });
        }

        const peopleFilters: NestedFilter[] = [];
        if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) {
            allDistinctPeople.delete(undefined);
            allDistinctPeople.delete(null);
            peopleFilters.push(
                new NestedFilter({
                    id: null,
                    label: this._translateService.instant('initials-avatar.unassigned'),
                }),
            );
        }

        this.#sortByName([...allDistinctPeople]).forEach(userId => {
            peopleFilters.push(
                new NestedFilter({
                    id: userId,
                    label: this._userService.getName(userId),
                }),
            );
        });
        filterGroups.push({
            slug: 'peopleFilters',
            label: this._translateService.instant('filters.assigned-people'),
            icon: 'red:user',
            filters: peopleFilters,
            checker: keyChecker('assignee'),
        });

        if (!this.#isDocumine) {
            const needsWorkFilters = [...allDistinctNeedsWork].map(
                item =>
                    new NestedFilter({
                        id: item,
                        label: workloadTranslations[item],
                        metadata: {
                            shape: AnnotationShapeMap[item],
                            color$:
                                item === 'image'
                                    ? this._dictionariesMapService.watch$(dossierTemplateId, item).pipe(map(e => e.hexColor))
                                    : this._defaultColorsService.getColor$(dossierTemplateId, annotationDefaultColorConfig[item]),
                        },
                    }),
            );
            needsWorkFilters.sort(RedactionFilterSorter.byKey);
            filterGroups.push({
                slug: 'needsWorkFilters',
                label: this._translateService.instant('filters.needs-work'),
                icon: 'red:needs-work',
                filterTemplate: needsWorkFilterTemplate,
                filters: needsWorkFilters,
                checker: (file: File, filter: INestedFilter) => annotationFilterChecker(file, filter, this._userService.currentUser.id),
                matchAll: true,
            });
        }

        const processingTypesFilters = [...allDistinctProcessingTypes].map(item => new NestedFilter({ id: item, label: item }));
        filterGroups.push({
            slug: 'processingTypeFilters',
            filters: processingTypesFilters,
            checker: (file: File, filter: INestedFilter) => file.processingType === filter.id,
            hide: true,
        });

        dynamicFilters.forEach((filterValue: Set<string>, filterKey: string) => {
            const id = filterKey.split(':')[0];
            const key = filterKey.split(':')[1];
            filterGroups.push({
                slug: key,
                label: key,
                icon: 'red:template',
                filters: [...filterValue].map(
                    (value?: string) =>
                        new NestedFilter({
                            // id shouldn't be undefined to work correctly
                            id: value ?? 'empty',
                            label: value ?? this._translateService.instant('filters.empty'),
                        }),
                ),
                checker: (input: File, filter: INestedFilter) => filter.id === (input.fileAttributes.attributeIdToValue[id] ?? 'empty'),
            });
        });

        filterGroups.push({
            slug: 'quickFilters',
            filters: this.#quickFilters(entities),
            checker: (file: File) =>
                checkedRequiredFilters().reduce((acc, f) => acc && f.checker(file), true) &&
                (checkedNotRequiredFilters().length === 0 || checkedNotRequiredFilters().reduce((acc, f) => acc || f.checker(file), false)),
        });

        const filesNamesFilters = entities.map(
            file =>
                new NestedFilter({
                    id: file.filename,
                    label: file.filename,
                }),
        );

        filterGroups.push({
            slug: 'filesNamesFilter',
            label: this._translateService.instant('dossier-overview.filters.label'),
            icon: 'iqser:document',
            filters: filesNamesFilters,
            checker: keyChecker('filename'),
            filterceptionPlaceholder: this._translateService.instant('dossier-overview.filters.search'),
        });

        return filterGroups;
    }

    _recentlyModifiedChecker = (file: File) => dayjs(file.lastUpdated).add(this.#config.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(dayjs());

    _assignedToMeChecker = (file: File) => file.assignee === this._userService.currentUser.id;

    _unassignedChecker = (file: File) => !file.assignee;

    _assignedToOthersChecker = (file: File) => file.assignee && file.assignee !== this._userService.currentUser.id;

    #getNewColumn(): WorkflowColumn<File, typeof WorkflowFileStatuses.NEW> {
        return {
            label: workflowFileStatusTranslations[WorkflowFileStatuses.NEW],
            key: WorkflowFileStatuses.NEW,
            enterFn: files => this._bulkActionsService.setToNew(files),
            enterPredicate: files => this._permissionsService.canSetToNew(files, this.#dossier),
            color: '#D3D5DA',
            entities: new BehaviorSubject([]),
        };
    }

    #getUnderReviewColumn(): WorkflowColumn<File, typeof WorkflowFileStatuses.UNDER_REVIEW> {
        return {
            label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_REVIEW],
            enterFn: async files => {
                const statuses: WorkflowFileStatus[] = [WorkflowFileStatuses.UNDER_APPROVAL, WorkflowFileStatuses.APPROVED];
                if (statuses.includes(files[0].workflowStatus)) {
                    await this._bulkActionsService.backToUnderReview(files);
                } else {
                    await this._bulkActionsService.assignToMe(files);
                }
            },
            enterPredicate: files => {
                const dossier = this.#dossier;
                return (
                    this._permissionsService.canSetUnderReview(files, dossier) ||
                    this._permissionsService.canAssignToSelf(files, dossier) ||
                    this._permissionsService.canAssignUser(files, dossier) ||
                    this._permissionsService.canUndoApproval(files, dossier)
                );
            },
            key: WorkflowFileStatuses.UNDER_REVIEW,
            color: '#FDBD00',
            entities: new BehaviorSubject([]),
        };
    }

    #getUnderApprovalColumn(): WorkflowColumn<File, typeof WorkflowFileStatuses.UNDER_APPROVAL> {
        return {
            label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_APPROVAL],
            enterFn: files => this._bulkActionsService.setToUnderApproval(files),
            enterPredicate: files => {
                const dossier = this.#dossier;
                return (
                    this._permissionsService.canSetUnderApproval(files, dossier) || this._permissionsService.canUndoApproval(files, dossier)
                );
            },
            key: WorkflowFileStatuses.UNDER_APPROVAL,
            color: '#374C81',
            entities: new BehaviorSubject([]),
        };
    }

    #getApprovedColumn(): WorkflowColumn<File, typeof WorkflowFileStatuses.APPROVED> {
        return {
            label: workflowFileStatusTranslations[WorkflowFileStatuses.APPROVED],
            enterFn: files => this._bulkActionsService.approve(files),
            enterPredicate: files => this._permissionsService.canBeApproved(files, this.#dossier),
            key: WorkflowFileStatuses.APPROVED,
            color: '#48C9F7',
            entities: new BehaviorSubject([]),
        };
    }

    #quickFilters(entities: File[]): NestedFilter[] {
        const recentPeriod = this.#config.RECENT_PERIOD_IN_HOURS;
        return [
            {
                id: 'recent',
                label: this._translateService.instant('dossier-overview.quick-filters.recent', {
                    hours: recentPeriod,
                }),
                required: true,
                checker: this._recentlyModifiedChecker,
                disabled: entities.filter(this._recentlyModifiedChecker).length === 0,
                helpModeKey: 'quick_filters_documents',
            },
            {
                id: 'assigned-to-me',
                label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
                checker: this._assignedToMeChecker,
                disabled: entities.filter(this._assignedToMeChecker).length === 0,
                helpModeKey: 'quick_filters_documents',
            },
            {
                id: 'unassigned',
                label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
                checker: this._unassignedChecker,
                disabled: entities.filter(this._unassignedChecker).length === 0,
                helpModeKey: 'quick_filters_documents',
            },
            {
                id: 'assigned-to-others',
                label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
                checker: this._assignedToOthersChecker,
                disabled: entities.filter(this._assignedToOthersChecker).length === 0,
                helpModeKey: 'quick_filters_documents',
            },
        ].map(filter => new NestedFilter(filter));
    }

    #openEditDossierDialog(dossierId: string) {
        this._dialogService.openDialog('editDossier', { dossierId });
    }

    #sortByName(ids: string[]) {
        return ids.sort((a, b) => this._userService.getName(a).localeCompare(this._userService.getName(b)));
    }
}
