// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { ServiceBusReceivedMessage } from '@azure/service-bus';
import * as grpc from '@grpc/grpc-js';
import {
    AbandonRequest,
    CompleteRequest,
    DeadletterRequest,
    DeferRequest,
    IServiceBusMessageActions,
    ReleaseSessionRequest,
    RenewMessageLockRequest,
    RenewSessionLockRequest,
    RenewSessionLockResponse,
    SetSessionStateRequest,
    SettlementServiceClient,
} from '../../types/settlement-types';
import { createGrpcClient } from '../grpcClientFactory';
import { encodePropertiesForOperation } from '../util/amqpPropertyEncoder';
import { GrpcUriBuilder } from '../util/grpcUriBuilder';

// Using the original proto-loader approach with better path resolution

// Client implementation with Promise-based methods
export class ServiceBusMessageActions implements IServiceBusMessageActions {
    private static instance: ServiceBusMessageActions | null = null;
    private client: SettlementServiceClient;

    private constructor() {
        const { uri, grpcMaxMessageLength } = GrpcUriBuilder.build();
        this.client = createGrpcClient<SettlementServiceClient>({
            serviceName: 'Settlement',
            address: uri,
            credentials: grpc.credentials.createInsecure(),
            grpcMaxMessageLength,
        });
    }

    static getInstance(): ServiceBusMessageActions {
        if (!ServiceBusMessageActions.instance) {
            ServiceBusMessageActions.instance = new ServiceBusMessageActions();
        }
        return ServiceBusMessageActions.instance;
    }

    // Add this private helper method to the ServiceBusMessageActions class
    private validateLockToken(message: ServiceBusReceivedMessage): string {
        const locktoken = message.lockToken;
        if (!locktoken) {
            throw new Error('ArgumentException: lockToken is required in ServiceBusReceivedMessage.');
        }
        return locktoken;
    }

    /**
     * Completes (settles) the specified message, removing it from the queue or subscription.
     *
     * @param message - The received Service Bus message to complete.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the lockToken is missing or the gRPC call fails.
     */
    async complete(message: ServiceBusReceivedMessage): Promise<void> {
        const locktoken = this.validateLockToken(message);
        return new Promise((resolve, reject) => {
            const request: CompleteRequest = { locktoken };
            this.client.complete(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Complete request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Abandons the specified message, making it available again for processing.
     *
     * @param message - The received Service Bus message to abandon.
     * @param propertiesToModify - Optional properties to modify on the message.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the lockToken is missing or the gRPC call fails.
     */
    async abandon(message: ServiceBusReceivedMessage, propertiesToModify?: Record<string, any>): Promise<void> {
        const locktoken = this.validateLockToken(message);
        const encodedProperties = encodePropertiesForOperation(propertiesToModify, 'abandon');

        return new Promise((resolve, reject) => {
            const request: AbandonRequest = {
                locktoken,
                propertiesToModify: encodedProperties,
            };

            this.client.abandon(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Abandon request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Deadletters the specified message, moving it to the dead-letter queue.
     *
     * @param message - The received Service Bus message to deadletter.
     * @param propertiesToModify - Optional properties to modify on the message.
     * @param deadletterReason - Optional reason for deadlettering the message.
     * @param deadletterErrorDescription - Optional error description for deadlettering.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the lockToken is missing or the gRPC call fails.
     */
    async deadletter(
        message: ServiceBusReceivedMessage,
        propertiesToModify?: Record<string, any>,
        deadletterReason?: string,
        deadletterErrorDescription?: string
    ): Promise<void> {
        const locktoken = this.validateLockToken(message);
        const encodedProperties = encodePropertiesForOperation(propertiesToModify, 'deadletter');

        return new Promise((resolve, reject) => {
            const request: DeadletterRequest = {
                locktoken,
                propertiesToModify: encodedProperties,
                deadletterReason: deadletterReason ? { value: deadletterReason } : undefined,
                deadletterErrorDescription: deadletterErrorDescription
                    ? { value: deadletterErrorDescription }
                    : undefined,
            };

            this.client.deadletter(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Deadletter request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Defers the specified message, making it invisible until retrieved by sequence number.
     *
     * @param message - The received Service Bus message to defer.
     * @param propertiesToModify - Optional properties to modify on the message.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the lockToken is missing or the gRPC call fails.
     */
    async defer(message: ServiceBusReceivedMessage, propertiesToModify?: Record<string, any>): Promise<void> {
        const locktoken = this.validateLockToken(message);
        const encodedProperties = encodePropertiesForOperation(propertiesToModify, 'defer');

        return new Promise((resolve, reject) => {
            const request: DeferRequest = {
                locktoken,
                propertiesToModify: encodedProperties,
            };

            this.client.defer(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Defer request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Renews the lock on the specified message, extending its lock duration.
     *
     * @param message - The received Service Bus message whose lock should be renewed.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the lockToken is missing or the gRPC call fails.
     */
    async renewMessageLock(message: ServiceBusReceivedMessage): Promise<void> {
        const locktoken = this.validateLockToken(message);
        return new Promise((resolve, reject) => {
            const request: RenewMessageLockRequest = { locktoken };

            this.client.renewMessageLock(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Renew message lock request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Sets the state for the specified session.
     *
     * @param sessionId - The session ID for which to set the state.
     * @param sessionState - The state to set for the session.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the gRPC call fails.
     */
    async setSessionState(sessionId: string, sessionState: Uint8Array): Promise<void> {
        return new Promise((resolve, reject) => {
            const request: SetSessionStateRequest = { sessionId, sessionState };

            this.client.setSessionState(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Set session state request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Releases the specified session, making it available for other receivers.
     *
     * @param sessionId - The session ID to release.
     * @returns A promise that resolves when the operation is successful.
     * @throws Error if the gRPC call fails.
     */
    async releaseSession(sessionId: string): Promise<void> {
        return new Promise((resolve, reject) => {
            const request: ReleaseSessionRequest = { sessionId };

            this.client.releaseSession(request, (error: grpc.ServiceError | null) => {
                if (error) {
                    console.error('Release session request failed:', {
                        code: error.code,
                        message: error.message,
                        details: error.details,
                    });
                    reject(error);
                } else {
                    resolve();
                }
            });
        });
    }

    /**
     * Renews the lock on the specified session, extending its lock duration.
     *
     * @param sessionId - The session ID whose lock should be renewed.
     * @returns A promise that resolves to the new locked-until date.
     * @throws Error if the gRPC call fails or if no response is returned.
     */
    async renewSessionLock(sessionId: string): Promise<Date> {
        return new Promise((resolve, reject) => {
            const request: RenewSessionLockRequest = { sessionId };

            this.client.renewSessionLock(
                request,
                (error: grpc.ServiceError | null, response?: RenewSessionLockResponse) => {
                    if (error) {
                        console.error('Renew session lock request failed:', {
                            code: error.code,
                            message: error.message,
                            details: error.details,
                        });
                        reject(error);
                    } else if (response && response.lockedUntil) {
                        resolve(response.lockedUntil);
                    } else {
                        const err = new Error('No response or lockedUntil returned from renewSessionLock');
                        reject(err);
                    }
                }
            );
        });
    }

    /**
     * Resets the singleton instance (for testing purposes).
     */
    static resetInstance(): void {
        ServiceBusMessageActions.instance = null;
    }
}
