import { TimeTrackingEntriesService, TimeTrackingEntryMessage } from '@humand/desert-eagle-grpc-ts-client';
import { BaseAdapter, Envs, getGrpcCredentials, LoggerPort, promisifyGrpcRequest, PromisifiedFunction } from '@humand-packages/common';
import { Service } from 'typedi';
import { getConfig } from '../../../../../../apiConfig/configSetup';
import { TimeTrackingAutoCloseEntry } from '../../../../business/models/timeTracking/autoCloseEntry';
import { AutoCloseEntryResult } from '../../../../business/models/timeTracking/timeTrackingHighAvailability';
import { TimeTrackingEntry } from '../../../../business/models/timeTrackingEntry';
import {
    autoCloseEntryResultToAutoCloseEntryResultGrpc,
    timeTrackingEntryToTimeTrackingEntryGrpc,
} from '../../../mappers/timeTracking/timeTrackingEntryMapper';
import { TimeTrackingEntriesRepositoryPort, UpdateEntriesResult } from '../../../ports/timeTracking/timeTrackingEntriesRepositoryPort';

@Service()
export default class TimeTrackingEntriesRepositoryAdapter extends BaseAdapter implements TimeTrackingEntriesRepositoryPort {
    constructor(private readonly logger: LoggerPort) {
        super();
        const config = getConfig();
        const env = config.env as Envs;
        this.client = new TimeTrackingEntriesService.TimeTrackingEntriesServiceClient(
            config.desertEagleServiceAddress || ':50051',
            getGrpcCredentials(env),
        );
        this.grpcCreateTimeTrackingEntry = promisifyGrpcRequest(this.client.createTimeTrackingEntry.bind(this.client));
        this.grpcUpdateEntries = promisifyGrpcRequest(this.client.updateEntries.bind(this.client));
        this.grpcProcessAutoCloseEntries = promisifyGrpcRequest(this.client.processAutoCloseEntries.bind(this.client));
    }

    private readonly client: TimeTrackingEntriesService.TimeTrackingEntriesServiceClient;
    private readonly grpcCreateTimeTrackingEntry: PromisifiedFunction<
        TimeTrackingEntryMessage.TimeTrackingEntryMessage,
        TimeTrackingEntryMessage.TimeTrackingEntryResponse
    >;

    private readonly grpcUpdateEntries: PromisifiedFunction<
        TimeTrackingEntryMessage.UpdateEntriesRequest,
        TimeTrackingEntryMessage.UpdateEntriesResponse
    >;

    private readonly grpcProcessAutoCloseEntries: PromisifiedFunction<
        TimeTrackingEntryMessage.ProcessAutoCloseEntriesRequest,
        TimeTrackingEntryMessage.ProcessAutoCloseEntriesResponse
    >;

    public createTimeTrackingEntry = async (
        entry: Partial<TimeTrackingEntry>,
        botAppId?: number,
    ): Promise<TimeTrackingEntryMessage.TimeTrackingEntryResponse> => {
        const request: TimeTrackingEntryMessage.TimeTrackingEntryMessage = {
            ...timeTrackingEntryToTimeTrackingEntryGrpc(entry, botAppId),
            instanceId: entry.instanceId!,
        };

        try {
            return await this.grpcCreateTimeTrackingEntry(request);
        } catch (error) {
            this.logger.error({ error }, 'Failed to create time tracking entry via gRPC');
            throw error;
        }
    };

    public updateEntries = async (
        instanceId: number,
        deletedEntries: TimeTrackingEntry[],
        modifiedEntries: Partial<TimeTrackingEntry>[],
        newEntries: Partial<TimeTrackingEntry>[],
    ): Promise<UpdateEntriesResult> => {
        try {
            const syncableDeletedEntries = deletedEntries.filter((entry) => !!entry.desertEagleEntryId);
            if (syncableDeletedEntries.length < deletedEntries.length) {
                this.logger.warn(
                    { skipped: deletedEntries.length - syncableDeletedEntries.length },
                    'Skipping delete of entries without desertEagleEntryId — entries never existed in Desert Eagle',
                );
            }

            if (syncableDeletedEntries.length === 0 && modifiedEntries.length === 0 && newEntries.length === 0) {
                return {
                    createdEntries: [],
                    deletedEntryIds: [],
                    message: 'No entries to sync',
                    success: true,
                    updatedEntries: [],
                };
            }

            const request: TimeTrackingEntryMessage.UpdateEntriesRequest = {
                deletedEntries: syncableDeletedEntries.map((entry) => {
                    return {
                        ...timeTrackingEntryToTimeTrackingEntryGrpc(entry),
                        instanceId: instanceId,
                    };
                }),
                modifiedEntries: modifiedEntries.map((entry) => {
                    return {
                        ...timeTrackingEntryToTimeTrackingEntryGrpc(entry),
                        instanceId: instanceId,
                    };
                }),
                newEntries: newEntries.map((entry) => {
                    return {
                        ...timeTrackingEntryToTimeTrackingEntryGrpc(entry),
                        instanceId: instanceId,
                    };
                }),
            };
            const response = await this.grpcUpdateEntries(request);

            return {
                createdEntries: response.createdEntries || [],
                deletedEntryIds: response.deletedEntryIds || [],
                message: response.message,
                success: response.success,
                updatedEntries: response.updatedEntries || [],
            };
        } catch (error) {
            this.logger.error(
                {
                    deletedCount: deletedEntries.length,
                    error,
                    modifiedCount: modifiedEntries.length,
                    newCount: newEntries.length,
                },
                'Failed to update time tracking entries via gRPC',
            );
            return {
                createdEntries: [],
                deletedEntryIds: [],
                message: 'Failed to sync entries',
                success: false,
                updatedEntries: [],
            };
        }
    };

    public processAutoCloseEntries = async (entries: TimeTrackingAutoCloseEntry[]): Promise<AutoCloseEntryResult[]> => {
        const request: TimeTrackingEntryMessage.ProcessAutoCloseEntriesRequest = {
            entries: entries.map((entry, index) => ({
                entry: timeTrackingEntryToTimeTrackingEntryGrpc(entry),
                index,
                overrideState: entry.overrideDesertEagleState || undefined,
            })),
        };
        try {
            const response = await this.grpcProcessAutoCloseEntries(request);
            return response.results.map((result) => autoCloseEntryResultToAutoCloseEntryResultGrpc(result));
        } catch (error) {
            this.logger.error({ error }, 'Error processing auto close entries via HA service');
            throw error;
        }
    };
}
