import { Injectable } from '@angular/core';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import {
    DashboardStats,
    Dictionary,
    Dossier,
    DOSSIERS_ARCHIVE,
    DossierTemplate,
    File,
    IComment,
    IDossier,
    ProcessingFileStatuses,
    WorkflowFileStatus,
    WorkflowFileStatuses,
} from '@red/domain';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
import { FeaturesService } from '@services/features.service';
import { FilesMapService } from '@services/files/files-map.service';
import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service';

@Injectable({ providedIn: 'root' })
export class PermissionsService {
    readonly #isDocumine = getConfig().IS_DOCUMINE;

    constructor(
        private readonly _userService: UserService,
        private readonly _filesMapService: FilesMapService,
        private readonly _featuresService: FeaturesService,
        private readonly _userPreferenceService: UserPreferenceService,
        private readonly _iqserPermissionsService: IqserPermissionsService,
        private readonly _dossierDictionariesMapService: DossierDictionariesMapService,
    ) {}

    get #userId(): string {
        return this._userService.currentUser.id;
    }

    canAddEditJustifications() {
        return this._userPreferenceService.isIqserDevMode && this.isAdmin() && this._iqserPermissionsService.has(Roles.legalBasis.write);
    }

    canEditGlobalFileAttributes() {
        return this._iqserPermissionsService.has(Roles.fileAttributes.writeConfig) && this.isAdmin();
    }

    canEditGlobalDossierAttributes() {
        return this._iqserPermissionsService.has(Roles.dossierAttributes.writeConfig) && this.isAdmin();
    }

    canDownloadEntityDictionary(): boolean {
        return this.isAdmin() || this.isManager();
    }

    canPerformDossierStatesActions() {
        return this.isAdmin() && this._iqserPermissionsService.has(Roles.states.write);
    }

    canEditEntities(): boolean {
        return this._iqserPermissionsService.has(Roles.dictionaryTypes.write) && this.isAdmin();
    }

    canDeleteEntities(entity: Dictionary | Dictionary[]): boolean {
        const entities = entity instanceof Dictionary ? [entity] : entity;
        return (
            entities.length &&
            this.isAdmin() &&
            this._iqserPermissionsService.has(Roles.dictionaryTypes.delete) &&
            entities.reduce((acc, _entity) => this.#canDeleteEntity(_entity) && acc, true)
        );
    }

    isAssigneeOrApprover(file: File, dossier: Dossier): boolean {
        return this.isFileAssignee(file) || this.isApprover(dossier);
    }

    displayReanalyseBtn(dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            this._iqserPermissionsService.has(Roles.dossiers.reanalyze) &&
            this.isApprover(dossier) &&
            !!this._filesMapService.get(dossier.id).find(f => f.analysisRequired)
        );
    }

    canUploadFiles(dossier: Dossier): boolean {
        return this._iqserPermissionsService.has(Roles.files.upload) && dossier.isActive;
    }

    canDownloadCsvReport(dossier: Dossier): boolean {
        return dossier.isActive;
    }

    canEditFileAttributes(file: File, dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            (((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier))) &&
            this._iqserPermissionsService.has(Roles.fileAttributes.write)
        );
    }

    canToggleAnalysis(file: File | File[], dossier: Dossier): boolean {
        if (!this._iqserPermissionsService.has(Roles.files.reanalyze)) {
            return false;
        }

        const files = file instanceof File ? [file] : file;
        const sameState = new Set(files.map(f => f.excluded)).size === 1;
        return sameState && files.reduce((acc, _file) => this.#canToggleAnalysis(_file, dossier) && acc, true);
    }

    showToggleAnalysis(dossier: Dossier): boolean {
        return this._iqserPermissionsService.has(Roles.excludeIncludeFile) && dossier.isActive;
    }

    canReanalyseFile(file: File | File[], dossier: Dossier) {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.reanalyze) &&
            files.reduce((acc, _file) => this.#canReanalyseFile(_file, dossier) && acc, true)
        );
    }

    canEnableAutoAnalysis(files: File[], dossier: Dossier) {
        if (!this._iqserPermissionsService.has(Roles.files.reanalyze)) {
            return false;
        }

        return files.reduce((acc, _file) => this.#canEnableAutoAnalysis(_file, dossier) && acc, true);
    }

    canDisableAutoAnalysis(files: File[], dossier: Dossier): boolean {
        return (
            this._iqserPermissionsService.has(Roles.files.reanalyze) &&
            files.reduce((acc, _file) => this.#canDisableAutoAnalysis(_file, dossier) && acc, true)
        );
    }

    isFileAssignee(file: File): boolean {
        return file.assignee === this.#userId;
    }

    canSoftDeleteFile(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.delete) &&
            files.reduce((acc, _file) => this.#canSoftDeleteFile(_file, dossier) && acc, true)
        );
    }

    canRestoreFile(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.delete) &&
            files.reduce((acc, _file) => this.#canRestoreFile(_file, dossier) && acc, true)
        );
    }

    canHardDeleteFile(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.delete) &&
            files.reduce((acc, _file) => this.#canHardDeleteFile(_file, dossier) && acc, true)
        );
    }

    canOcrFile(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.reanalyze) &&
            files.reduce((acc, _file) => this.#canOcrFile(_file, dossier) && acc, true)
        );
    }

    canAssignToSelf(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.setReviewer) &&
            files.reduce((acc, _file) => this.#canAssignToSelf(_file, dossier) && acc, true)
        );
    }

    canAssignUser(file: File | File[], dossier: Dossier, nextStatus?: WorkflowFileStatus): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.setReviewer) &&
            files.reduce((acc, _file) => this.#canAssignUser(_file, dossier, nextStatus) && acc, true)
        );
    }

    canUnassignUser(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.setReviewer) &&
            files.reduce((acc, _file) => this.#canUnassignUser(_file, dossier) && acc, true)
        );
    }

    canSetToNew(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return files.reduce((acc, _file) => this.#canSetToNew(_file, dossier) && acc, true);
    }

    canSetUnderReview(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return files.reduce((acc, _file) => this.#canSetUnderReview(_file, dossier) && acc, true);
    }

    canBeApproved(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.setApproved) &&
            files.reduce((acc, _file) => this.#canBeApproved(_file, dossier) && acc, true)
        );
    }

    isReadyForApproval(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.setApproved) &&
            files.reduce((acc, _file) => this.#isReadyForApproval(_file, dossier) && acc, true)
        );
    }

    canSetUnderApproval(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.setUnderApproval) &&
            files.reduce((acc, _file) => this.#canSetUnderApproval(_file, dossier) && acc, true)
        );
    }

    isOwner(dossier: IDossier): boolean {
        return dossier.ownerId === this.#userId;
    }

    isApprover(dossier: Dossier, userId = this.#userId): boolean {
        return dossier.approverIds.indexOf(userId) >= 0;
    }

    isDossierMember(dossier: IDossier): boolean {
        return dossier.memberIds.includes(this.#userId);
    }

    canPerformAnnotationActions(file: File, dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            !file.isUnprocessed &&
            !file.deleted &&
            !file.isError &&
            !file.isFullProcessing &&
            !file.excluded &&
            !!file.lastProcessed &&
            (file.isUnderReview || file.isUnderApproval) &&
            this.isFileAssignee(file)
        );
    }

    canUndoApproval(file: File | File[], dossier: Dossier): boolean {
        const files = file instanceof File ? [file] : file;
        return (
            this._iqserPermissionsService.has(Roles.files.setApproved) &&
            files.reduce((acc, _file) => this.#canUndoApproval(_file, dossier) && acc, true)
        );
    }

    canMarkPagesAsViewed(file: File): boolean {
        return (
            this._iqserPermissionsService.has(Roles.manageViewedPages) &&
            (file.isUnderReview || file.isUnderApproval) &&
            this.isFileAssignee(file)
        );
    }

    canDownloadFiles(files: File[], dossier: Dossier): boolean {
        if (files.length === 0) {
            return false;
        }
        return this.isApprover(dossier) && files.reduce((prev, file) => prev && !file.isInitialProcessing, true);
    }

    canDeleteDossier(dossier: IDossier): boolean {
        return this._iqserPermissionsService.has(Roles.dossiers.delete) && this.isOwner(dossier);
    }

    canRestoreDossier(dossier: IDossier): boolean {
        return this._iqserPermissionsService.has(Roles.dossiers.delete) && this.isOwner(dossier);
    }

    canCreateDossier(dossierTemplate: DossierTemplate | DashboardStats): boolean {
        return this._iqserPermissionsService.has(Roles.dossiers.write) && this.isManager() && dossierTemplate.isActive;
    }

    canArchiveDossier(dossier: Dossier): boolean {
        return (
            this._iqserPermissionsService.has(Roles.dossiers.archived) &&
            this._featuresService.isEnabled(DOSSIERS_ARCHIVE) &&
            dossier.isActive &&
            this.isOwner(dossier)
        );
    }

    canEditDossier(dossier: Dossier): boolean {
        return this._iqserPermissionsService.has(Roles.dossiers.edit) && this.isManager() && !!dossier?.ownerId;
    }

    canEditDossierDictionary(dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            this._dossierDictionariesMapService.get(dossier.id).length > 0 &&
            this.isDossierMember(dossier) &&
            this._iqserPermissionsService.has(Roles.dossiers.dictionaryEntries.write)
        );
    }

    canEditDossierAttributes(dossier: Dossier): boolean {
        return dossier.isActive && this.isOwner(dossier) && this._iqserPermissionsService.has(Roles.dossierAttributes.write);
    }

    canEditTeamMembers(): boolean {
        return this._iqserPermissionsService.has(Roles.dossiers.edit) && this.isManager();
    }

    isAdmin(): boolean {
        return this._userService.currentUser.isAdmin;
    }

    isManager(): boolean {
        return this._userService.currentUser.isManager;
    }

    canAddComment(file: File, dossier: Dossier): boolean {
        return this._iqserPermissionsService.has(Roles.comments.write) && this.isAssigneeOrApprover(file, dossier) && !file.isApproved;
    }

    canExcludePages(file: File, dossier: Dossier): boolean {
        return this._iqserPermissionsService.has(Roles.excludeIncludePages) && this.canPerformAnnotationActions(file, dossier);
    }

    canDeleteComment(comment: IComment, file: File, dossier: Dossier) {
        return (
            this._iqserPermissionsService.has(Roles.comments.delete) &&
            (comment.user === this.#userId || this.isApprover(dossier)) &&
            !file.isApproved
        );
    }

    canImportRedactions(file: File, dossier: Dossier) {
        return (
            this._iqserPermissionsService.has(Roles.redactions.write) &&
            dossier.isActive &&
            this.isFileAssignee(file) &&
            !file.isApproved &&
            !!file.lastProcessed
        );
    }

    canRotatePage(file: File) {
        return this.isFileAssignee(file) && this._iqserPermissionsService.has(Roles.files.rotatePage);
    }

    canDownloadRedactedFile() {
        return this._iqserPermissionsService.has(Roles.files.processDownload);
    }

    canEditRules() {
        return this._iqserPermissionsService.has(Roles.rules.write) && this.isAdmin();
    }

    canViewRssDialog() {
        return this._iqserPermissionsService.has(Roles.getRss);
    }

    #canDeleteEntity(entity: Dictionary): boolean {
        return !entity.systemManaged;
    }

    #canOcrFile(file: File, dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            !!file.lastProcessed &&
            file.canBeOCRed &&
            ((this.isDossierMember(dossier) && !file.assignee) || this.isFileAssignee(file))
        );
    }

    #canToggleAnalysis(file: File, dossier: Dossier): boolean {
        return (
            dossier.isActive &&
            !!file.lastProcessed &&
            this.isFileAssignee(file) &&
            (file.isNew || file.isUnderReview || file.isUnderApproval)
        );
    }

    #canSoftDeleteFile(file: File, dossier: Dossier): boolean {
        return dossier.isActive && (this.isAssigneeOrApprover(file, dossier) || (!file.assignee && this.isDossierMember(dossier)));
    }

    #canRestoreFile(file: File, dossier: Dossier): boolean {
        return this.isAssigneeOrApprover(file, dossier);
    }

    #canHardDeleteFile(file: File, dossier: Dossier): boolean {
        return this.isAssigneeOrApprover(file, dossier) || (!file.assignee && this.isDossierMember(dossier));
    }

    #canReanalyseFile(file: File, dossier: Dossier): boolean {
        return dossier.isActive && this.isAssigneeOrApprover(file, dossier) && file.analysisRequired;
    }

    #canEnableAutoAnalysis(file: File, dossier: Dossier): boolean {
        const canEnable = dossier.isActive && this.isFileAssignee(file) && !file.isApproved && !!file.lastProcessed;
        if (!canEnable) {
            return false;
        }

        if (this.#isDocumine) {
            return file.excludedFromAutomaticAnalysis || file.processingStatus === ProcessingFileStatuses.ERROR;
        }

        return file.excludedFromAutomaticAnalysis;
    }

    #canDisableAutoAnalysis(file: File, dossier: Dossier): boolean {
        return (
            dossier.isActive && !file.excludedFromAutomaticAnalysis && this.isFileAssignee(file) && !file.isApproved && !!file.lastProcessed
        );
    }

    /** UNDER_REVIEW => NEW */
    #canSetToNew(file: File, dossier: Dossier): boolean {
        return dossier.isActive && file.isUnderReview && this.isAssigneeOrApprover(file, dossier);
    }

    /** UNDER_REVIEW => UNDER_APPROVAL */
    #canSetUnderApproval(file: File, dossier: Dossier): boolean {
        const hasRss = this._iqserPermissionsService.has(Roles.getRss);
        const fileCanBeSetUnderApproval = !hasRss && file.isUnderReview;
        return dossier.isActive && fileCanBeSetUnderApproval && this.isAssigneeOrApprover(file, dossier);
    }

    /** UNDER_APPROVAL => UNDER_REVIEW OR NEW => UNDER_REVIEW */
    #canSetUnderReview(file: File, dossier: Dossier): boolean {
        if (!dossier.isActive) {
            return false;
        }

        const hasRss = this._iqserPermissionsService.has(Roles.getRss);
        const fileCanBeSetUnderReview = file.isUnderApproval || (hasRss && file.isApproved);
        return fileCanBeSetUnderReview && this.isAssigneeOrApprover(file, dossier);
    }

    /** UNDER_APPROVAL => APPROVED */
    #canBeApproved(file: File, dossier: Dossier): boolean {
        return this.#isReadyForApproval(file, dossier) && file.canBeApproved;
    }

    #isReadyForApproval(file: File, dossier: Dossier) {
        const hasRss = this._iqserPermissionsService.has(Roles.getRss);
        const fileCanBeApproved = file.isUnderApproval || (hasRss && file.isUnderReview);
        return dossier.isActive && fileCanBeApproved && this.isAssigneeOrApprover(file, dossier);
    }

    /** APPROVED => UNDER_APPROVAL */
    #canUndoApproval(file: File, dossier: Dossier): boolean {
        const hasRss = this._iqserPermissionsService.has(Roles.getRss);
        const fileCanBeSetUnderApproval = !hasRss && file.isApproved;
        return dossier.isActive && fileCanBeSetUnderApproval && this.isApprover(dossier);
    }

    #fileIsOk(file: File, dossier: Dossier) {
        return dossier.isActive && !file.isError && !file.isProcessing && !!file.lastProcessed;
    }

    #canAssignToSelf(file: File, dossier: Dossier): boolean {
        const precondition = this.#fileIsOk(file, dossier) && !this.isFileAssignee(file);
        return precondition && (this.isApprover(dossier) || (this.isDossierMember(dossier) && (file.isNew || file.isUnderReview)));
    }

    #canAssignUser(file: File, dossier: Dossier, nextStatus?: WorkflowFileStatus) {
        if (!this.#fileIsOk(file, dossier)) {
            return false;
        }

        if (nextStatus === WorkflowFileStatuses.UNDER_REVIEW) {
            return this.#canAssignReviewer(file, dossier);
        }

        if (nextStatus === WorkflowFileStatuses.UNDER_APPROVAL || nextStatus === WorkflowFileStatuses.APPROVED) {
            return this.#canAssignApprover(file, dossier);
        }

        if ((file.isNew || file.isUnderReview) && this.isDossierMember(dossier)) {
            return true;
        }

        if ((file.isUnderApproval || file.isApproved) && dossier.approverIds.length > 1 && this.isApprover(dossier)) {
            return true;
        }
    }

    #canAssignReviewer(file: File, dossier: Dossier) {
        const fileStatesForReviewer = file.isNew || file.isUnderReview || file.isUnderApproval;
        return fileStatesForReviewer && this.isDossierMember(dossier);
    }

    #canAssignApprover(file: File, dossier: Dossier) {
        const fileStatesForApprover = file.isUnderReview || file.isUnderApproval || file.isApproved;
        return fileStatesForApprover && dossier.hasApprovers && this.isApprover(dossier);
    }

    #canUnassignUser(file: File, dossier: Dossier) {
        return this.#fileIsOk(file, dossier) && !!file.assignee && this.isAssigneeOrApprover(file, dossier);
    }
}
