import {
    Channel,
    LanguagesValues,
    NotificationContentItem,
    Priority,
    NotificationType as ServiceNotificationType,
} from '@humand/notification-packages';
import { CapabilityName, I18n, LoggerPort, Modules as CommonModules, NotificationsAdapter, UserStatus } from '@humand-packages/common';
import { Service } from 'typedi';
import { PortsDI } from '../../../../../config/portsDI';
import { HumandNotification, NotificationType } from '../../../../../models/firebaseTokens/notification';
import { CloudNotificationsService } from '../../../../../services/cloudNotifications';
import { DeepLinksService } from '../../../../../services/deepLinks';
import { Instance } from '../../../../instances/business/models/instance';
import { NotificationsService } from '../../../../notifications/business/services/notificationsService';
import { Modules as NotificationModules } from '../../../../notifications/business/types/modules';
import { NotificationInputByKind } from '../../../../notifications/business/types/notificationInput';
import { OrganizationChartsService } from '../../../../organizationCharts/business/services/organizationChartsService';
import { PermissionsService } from '../../../../permissions/business/services/permissionsService';
import { UsersService } from '../../../../users/business/services/usersService';
import { UserDAO } from '../../../../users/infrastructure/dataAccessObjects/userDAO';
import {
    STEP_PENDING_NO_APPROVERS,
    STEP_PENDING_WITH_APPROVERS,
    StepPendingNotificationVariant,
} from '../../../business/constants/categorizedHourNotifications';
import { INCIDENCE_NOTIFICATION_VARIANTS } from '../../../business/constants/incidenceNotifications';
import TimeTrackingNotificationsPort from '../../../business/ports/timeTrackingNotificationsPort';
import { TimeTrackingCategorizedHourApprovalService } from '../../../business/services/timeTracking/timeTrackingCategorizedHourApprovalService';
import { NotifyIncidenceParams } from '../../../business/types/timeTracking/notifyIncidenceParams';
import { TimeTrackingCategorizedHourApprovalStepState } from '../../constants/timeTracking/categorizedHourApprovalStepStates';
import { TimeTrackingHourCategorizationRepositoryPort } from '../../ports/timeTracking/timeTrackingHourCategorizationRepositoryPort';

@Service()
export class TimeTrackingNotificationsAdapter extends NotificationsAdapter implements TimeTrackingNotificationsPort {
    constructor(
        private readonly cloudNotificationService: CloudNotificationsService,
        private readonly logger: LoggerPort,
        private readonly approvalService: TimeTrackingCategorizedHourApprovalService,
        private readonly notificationsService: NotificationsService,
        private readonly permissionsService: PermissionsService,
        private readonly usersService: UsersService,
        private readonly deepLinksService: DeepLinksService,
        @PortsDI(TimeTrackingHourCategorizationRepositoryPort)
        private readonly hourCategorizationRepository: TimeTrackingHourCategorizationRepositoryPort,
        private readonly organizationChartsService: OrganizationChartsService,
    ) {
        super();
    }

    public sendSilentPush = async (users: UserDAO[], instance: Instance, isDisablingNotifications = false): Promise<void> => {
        const hasSchedules = !isDisablingNotifications;
        const notification = new HumandNotification('', '', NotificationType.TIME_TRACKING_NOTIFICATION_SCHEDULE_UPDATED, {
            hasSchedules: hasSchedules.toString(),
        });
        notification.noDisplay = true;

        await this.cloudNotificationService.sendNotification(notification, users, instance.getId());
    };

    public notifyCategorizedHourStepPending = async (instanceId: number, categorizedHourId: string, approvalStepId: string): Promise<void> => {
        const [categorizedHour, steps] = await Promise.all([
            this.hourCategorizationRepository.getCategorizedHour(instanceId, categorizedHourId),
            this.approvalService.getCategorizedHourApprovalSteps(instanceId, categorizedHourId),
        ]);
        if (!categorizedHour) return;

        const step = steps.find((s) => s.id === approvalStepId);
        if (!step || step.state !== TimeTrackingCategorizedHourApprovalStepState.PENDING) return;

        const [collaborator] = await this.usersService.findByPks([categorizedHour.userId], {
            attributes: ['id', 'firstName', 'lastName'],
            status: [UserStatus.ACTIVE, UserStatus.UNCLAIMED],
            withFirebaseToken: false,
        });
        if (!collaborator) return;

        const approverIds = step.potentialApprovers.map((pa) => pa.userId);

        if (approverIds.length > 0) {
            const notification = await this.buildStepPendingNotification(
                instanceId,
                categorizedHourId,
                approvalStepId,
                approverIds,
                collaborator,
                STEP_PENDING_WITH_APPROVERS,
            );
            if (!notification) return;
            await this.notificationsService.queueNotification([Channel.BELL, Channel.PUSH] as const, notification, Priority.LOW);
            return;
        }

        const adminIds = await this.permissionsService.getUserIdsOfCapabilityUnpaginated({
            instanceId,
            capabilityName: CapabilityName.MANAGE_TIME_TRACKING,
        });
        if (adminIds.length === 0) return;

        const notification = await this.buildStepPendingNotification(
            instanceId,
            categorizedHourId,
            approvalStepId,
            adminIds,
            collaborator,
            STEP_PENDING_NO_APPROVERS,
        );
        if (!notification) return;
        await this.notificationsService.queueNotification([Channel.BELL, Channel.PUSH] as const, notification, Priority.LOW);
    };

    public notifyIncidence = async (params: NotifyIncidenceParams): Promise<void> => {
        const variant = INCIDENCE_NOTIFICATION_VARIANTS[params.incidenceType];
        if (!variant) return;

        const bossIds = await this.organizationChartsService.getBossIds(params.userId, params.instanceId);
        if (bossIds.length === 0) return;

        const capableIds = await this.permissionsService.getUserIdsOfCapabilityUnpaginated({
            instanceId: params.instanceId,
            capabilityName: CapabilityName.VIEW_TIME_TRACKING,
        });
        const recipientIds = bossIds.filter((id) => capableIds.includes(id));
        if (recipientIds.length === 0) return;

        const recipients = await this.usersService.findByPks(recipientIds, {
            attributes: ['id', 'language'],
            status: [UserStatus.ACTIVE, UserStatus.UNCLAIMED],
            withFirebaseToken: false,
        });
        if (recipients.length === 0) return;

        const [user] = await this.usersService.findByPks([params.userId], {
            attributes: ['id', 'firstName', 'lastName'],
            status: [UserStatus.ACTIVE, UserStatus.UNCLAIMED],
            withFirebaseToken: false,
        });
        if (!user) return;

        const content = this.groupContentByLanguage(recipients, (language) => {
            const t = I18n.translateModule(language, CommonModules.TIME_TRACKING, [
                {
                    key: variant.titleKey,
                    params: {
                        employeeFirstName: user.firstName,
                        employeeLastName: user.lastName,
                        interpolation: { escapeValue: false },
                    },
                },
                {
                    key: variant.descriptionKey,
                    params: {
                        employeeFirstName: user.firstName,
                        time: params.time,
                        lateMinutes: params.lateMinutes !== undefined ? String(params.lateMinutes) : undefined,
                        interpolation: { escapeValue: false },
                    },
                },
            ]);
            return { title: t[variant.titleKey], description: t[variant.descriptionKey] };
        });

        // Notification framework's extraData requires Record<string, string>; consumers parse back to number.
        const extraData = {
            userId: params.userId.toString(),
            dateString: params.dateString,
            incidenceType: params.incidenceType,
        };

        await this.notificationsService.queueNotification(
            [Channel.BELL, Channel.PUSH] as const,
            {
                instanceId: params.instanceId,
                module: NotificationModules.TIME_TRACKING,
                type: variant.type,
                notificationType: ServiceNotificationType.GROUPED_WITH_MANY_LANGUAGES as const,
                deepLink: this.deepLinksService.getLinkToTimeTrackingIncidence(params.userId, params.dateString, params.incidenceType),
                bell: { extraData },
                push: { deliveryConfiguration: { withSound: true }, extraData },
                content,
            },
            Priority.LOW,
        );
    };

    private buildStepPendingNotification = async (
        instanceId: number,
        categorizedHourId: string,
        approvalStepId: string,
        recipientIds: number[],
        collaborator: { id: number; firstName: string; lastName: string },
        variant: StepPendingNotificationVariant,
    ): Promise<NotificationInputByKind<
        typeof ServiceNotificationType.GROUPED_WITH_MANY_LANGUAGES,
        readonly [typeof Channel.BELL, typeof Channel.PUSH]
    > | null> => {
        const recipients = await this.usersService.findByPks(recipientIds, {
            attributes: ['id', 'language'],
            status: [UserStatus.ACTIVE, UserStatus.UNCLAIMED],
            withFirebaseToken: false,
        });
        if (recipients.length === 0) return null;

        const content = this.groupContentByLanguage(recipients, (language) => {
            const translation = I18n.translateModule(language, CommonModules.TIME_TRACKING, [
                {
                    key: variant.titleKey,
                    params: {
                        collaboratorFirstName: collaborator.firstName,
                        collaboratorLastName: collaborator.lastName,
                        interpolation: { escapeValue: false },
                    },
                },
                {
                    key: variant.descriptionKey,
                    params: {
                        collaboratorFirstName: collaborator.firstName,
                        interpolation: { escapeValue: false },
                    },
                },
            ]);
            return {
                title: translation[variant.titleKey],
                description: translation[variant.descriptionKey],
            };
        });

        const extraData = { categorizedHourId, approvalStepId };
        return {
            instanceId,
            module: NotificationModules.TIME_TRACKING,
            type: variant.type,
            notificationType: ServiceNotificationType.GROUPED_WITH_MANY_LANGUAGES as const,
            deepLink: this.deepLinksService.getLinkToCategorizedHour(categorizedHourId),
            bell: { extraData },
            push: { deliveryConfiguration: { withSound: true }, extraData },
            content,
        };
    };

    private groupContentByLanguage = <U extends { id: number; language: LanguagesValues }>(
        users: U[],
        translate: (language: LanguagesValues) => { title: string; description: string },
    ): NotificationContentItem<typeof ServiceNotificationType.GROUPED_WITH_MANY_LANGUAGES, readonly [typeof Channel.BELL, typeof Channel.PUSH]>[] => {
        const contentMap = users.reduce(
            (acc, user) => {
                const { language } = user;
                if (!acc[language]) {
                    const t = translate(language);
                    acc[language] = {
                        description: t.description,
                        language,
                        title: t.title,
                        userIds: [] as number[],
                    };
                }
                acc[language].userIds.push(user.id);
                return acc;
            },
            {} as Record<
                LanguagesValues,
                NotificationContentItem<
                    typeof ServiceNotificationType.GROUPED_WITH_MANY_LANGUAGES,
                    readonly [typeof Channel.BELL, typeof Channel.PUSH]
                >
            >,
        );
        return Object.values(contentMap);
    };
}
