import { Inject, Injectable, OnDestroy } from '@angular/core';
import { EntitiesService, getConfig, QueryParam } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { EMPTY, firstValueFrom, iif, merge, Observable, of, Subscription, timer } from 'rxjs';
import { AppConfig, Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
import { map, switchMap, tap } from 'rxjs/operators';
import { notificationsTranslations } from '@translations/notifications-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@users/user.service';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { DossiersCacheService } from './dossiers/dossiers-cache.service';
import dayjs from 'dayjs';
import { BASE_HREF, List, mapEach } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';

const INCLUDE_SEEN = false;

const AVAILABLE_NOTIFICATIONS_DAYS = 30;
const AVAILABLE_OLD_NOTIFICATIONS_MINUTES = 60;
const NOTIFICATIONS_THRESHOLD = 1000;

@Injectable({
    providedIn: 'root',
})
export class NotificationsService extends EntitiesService<INotification, Notification> implements OnDestroy {
    readonly #config = getConfig<AppConfig>();
    readonly #subscription = new Subscription();
    protected readonly _defaultModelPath = 'notification';
    protected readonly _entityClass = Notification;

    constructor(
        @Inject(BASE_HREF) private readonly _baseHref: string,
        private readonly _translateService: TranslateService,
        private readonly _tenantsService: TenantsService,
        private readonly _userService: UserService,
        private readonly _dossiersCacheService: DossiersCacheService,
    ) {
        super();

        if (!this._dossiersCacheService.empty) {
            this._dossiersCacheService.load().then(async () => await firstValueFrom(this.loadAll()));
        }

        this.#subscription.add(this.#initTimerAndChanges().subscribe());
    }

    ngOnDestroy() {
        this.#subscription.unsubscribe();
    }

    async toggleNotificationRead(body: List, setRead: boolean) {
        let queryParam: QueryParam;
        if (setRead !== undefined && setRead !== null) {
            queryParam = { key: 'setRead', value: setRead };
        }

        await firstValueFrom(this._post(body, `${this._defaultModelPath}/toggle-read`, [queryParam]));
        await firstValueFrom(this.loadAll());
    }

    loadAll(includeSeen = INCLUDE_SEEN): Observable<Notification[]> {
        const queryParam: QueryParam = { key: 'includeSeen', value: includeSeen };

        return this.getAll<{ notifications: Notification[] }>(this._defaultModelPath, [queryParam]).pipe(
            map(response => this.#filterNotifications(response.notifications)),
            mapEach(notification => this._new(notification)),
            tap(notifications => this.setEntities(notifications)),
        );
    }

    private _new(notification: INotification) {
        const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
        return new Notification(notification, message);
    }

    private _translate(notification: INotification, translation: string): string {
        const fileId = notification.target.fileId;
        const dossierId = notification.target.dossierId;
        const dossier = this._dossiersCacheService.get(dossierId);
        const fileName = notification.target.fileName;
        const dossierName = notification.target?.dossierName ?? dossier?.dossierName;
        const downloadHref = `/ui/${this._tenantsService.activeTenantId}/main/downloads`;

        return this._translateService.instant(translation, {
            fileHref: this._getFileHref(dossier, fileId),
            dossierHref: this._getDossierHref(dossier),
            dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
            fileName: fileName ?? this._translateService.instant(_('file')),
            user: this._getUsername(notification.userId),
            downloadHref: downloadHref,
        });
    }

    private _getFileHref(dossier: Dossier, fileId: string): string {
        return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
    }

    private _getDossierHref(dossier: Dossier): string {
        return dossier ? `${this._baseHref}/${this._tenantsService.activeTenantId}${dossier.routerLink}` : null;
    }

    private _getUsername(userId: string | undefined) {
        return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
    }

    #initTimerAndChanges() {
        const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe(
            switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
            switchMap(() => this.#loadNotificationsIfChanged()),
        );

        // Rebuild notifications when cached dossiers are updated
        const changed$ = this._dossiersCacheService.changed$.pipe(tap(() => this.setEntities(this.all.map(e => this._new(e)))));
        return merge(timer$, changed$);
    }

    #filterNotifications(notifications: Notification[]): Notification[] {
        const todayDate = dayjs(new Date());

        notifications = notifications.filter(n => {
            if (!(n.notificationType in NotificationTypes)) {
                return false;
            }

            const readDate = dayjs(n.readDate);
            if (!readDate.isValid()) {
                return true;
            }

            const creationDate = dayjs(n.creationDate);
            if (todayDate.diff(creationDate, 'day') <= (this.#config.AVAILABLE_NOTIFICATIONS_DAYS ?? AVAILABLE_NOTIFICATIONS_DAYS)) {
                return true;
            }

            return (
                todayDate.diff(readDate, 'minute') <=
                (this.#config.AVAILABLE_OLD_NOTIFICATIONS_MINUTES ?? AVAILABLE_OLD_NOTIFICATIONS_MINUTES)
            );
        });

        return notifications.slice(0, this.#config.NOTIFICATIONS_THRESHOLD ?? NOTIFICATIONS_THRESHOLD);
    }

    #loadNotificationsIfChanged(): Observable<Notification[]> {
        return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll(), EMPTY)));
    }
}
