/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { BaseClientOptions } from '../client';
import { MessageListener } from './MessageListener';
import { OffsetOption } from './OffsetOption';
import { LitePushConsumerImpl } from './LitePushConsumerImpl';

/**
 * LitePushConsumer interface for consuming messages from lite topics.
 *
 * <p>LitePushConsumer is a specialized consumer designed for lightweight scenarios
 * with reduced metadata and storage overhead. It supports dynamic subscription
 * management for lite topics.</p>
 */
export interface LitePushConsumer {
  /**
   * Subscribe to a lite topic.
   *
   * <p>The subscribeLite() method initiates network requests and performs quota verification,
   * so it may fail. It's important to check the result of this call to ensure that the
   * subscription was successfully added. Possible failure scenarios include:</p>
   * <ul>
   *   <li>Network request errors, which can be retried.</li>
   *   <li>Quota verification failures, indicated by LiteSubscriptionQuotaExceededException.
   *       In this case, evaluate whether the quota is insufficient and promptly unsubscribe
   *       from unused subscriptions using unsubscribeLite() to free up resources.</li>
   * </ul>
   *
   * @param liteTopic - The name of the lite topic to subscribe
   * @throws ClientException if an error occurs during subscription
   */
  subscribeLite(liteTopic: string): Promise<void>;

  /**
   * Subscribe to a lite topic with consumeFromOption to specify the consume from offset.
   *
   * @param liteTopic - The name of the lite topic to subscribe
   * @param offsetOption - The consume from offset option
   * @throws ClientException if an error occurs during subscription
   */
  subscribeLite(liteTopic: string, offsetOption: OffsetOption): Promise<void>;

  /**
   * Unsubscribe from a lite topic.
   *
   * @param liteTopic - The name of the lite topic to unsubscribe from
   * @throws ClientException if an error occurs during unsubscription
   */
  unsubscribeLite(liteTopic: string): Promise<void>;

  /**
   * Get the lite topic immutable set.
   *
   * @return Lite topic immutable set
   */
  getLiteTopicSet(): Set<string>;

  /**
   * Get the load balancing group for the consumer.
   *
   * @return Consumer load balancing group
   */
  getConsumerGroup(): string;

  /**
   * Close the consumer and release all related resources.
   *
   * <p>Once the consumer is closed, <strong>it could not be started once again.</strong>
   * We maintain an FSM (finite-state machine) to record the different states for each
   * push consumer.</p>
   */
  close(): Promise<void>;
}

export interface LitePushConsumerOptions extends BaseClientOptions {
  consumerGroup: string;
  bindTopic: string;
  messageListener: MessageListener;
  maxCacheMessageCount?: number;
  maxCacheMessageSizeInBytes?: number;
  consumptionThreadCount?: number;
  enableFifoConsumeAccelerator?: boolean;
}

const CONSUMER_GROUP_PATTERN = /^[a-zA-Z0-9_-]+$/;

/**
 * LitePushConsumer builder class.
 *
 * <p>This class provides a fluent API for configuring and creating
 * lite push consumers with reduced overhead for lightweight scenarios.</p>
 */
export class LitePushConsumerBuilder {
  private options: Partial<LitePushConsumerOptions> = {};

  /**
   * Set the bind topic for the lite push consumer.
   *
   * @param bindTopic - The parent topic to bind
   * @return This builder instance
   * @throws Error if bindTopic is blank
   */
  bindTopic(bindTopic: string): LitePushConsumerBuilder {
    if (!bindTopic || bindTopic.trim().length === 0) {
      throw new Error('bindTopic should not be blank');
    }
    this.options.bindTopic = bindTopic;
    return this;
  }

  /**
   * Set the client configuration.
   *
   * @param options - Client configuration options
   * @return This builder instance
   * @throws Error if options is null/undefined
   */
  setClientConfiguration(options: BaseClientOptions): LitePushConsumerBuilder {
    if (!options) {
      throw new Error('clientConfiguration should not be null');
    }
    Object.assign(this.options, options);
    return this;
  }

  /**
   * Set the consumer group.
   *
   * @param consumerGroup - Consumer group name
   * @return This builder instance
   * @throws Error if consumerGroup is null, doesn't match the pattern, or starts with 'GID-'
   */
  setConsumerGroup(consumerGroup: string): LitePushConsumerBuilder {
    if (!consumerGroup) {
      throw new Error('consumerGroup should not be null');
    }
    if (!CONSUMER_GROUP_PATTERN.test(consumerGroup)) {
      throw new Error(`consumerGroup does not match the pattern ${CONSUMER_GROUP_PATTERN.source}`);
    }
    this.options.consumerGroup = consumerGroup;
    return this;
  }

  /**
   * Set the message listener.
   *
   * @param messageListener - Message listener implementation
   * @return This builder instance
   * @throws Error if messageListener is null/undefined
   */
  setMessageListener(messageListener: MessageListener): LitePushConsumerBuilder {
    if (!messageListener) {
      throw new Error('messageListener should not be null');
    }
    this.options.messageListener = messageListener;
    return this;
  }

  /**
   * Set the maximum cache message count.
   *
   * @param maxCacheMessageCount - Maximum number of messages to cache
   * @return This builder instance
   * @throws Error if maxCacheMessageCount is not positive
   */
  setMaxCacheMessageCount(maxCacheMessageCount: number): LitePushConsumerBuilder {
    if (maxCacheMessageCount <= 0) {
      throw new Error('maxCacheMessageCount should be positive');
    }
    this.options.maxCacheMessageCount = maxCacheMessageCount;
    return this;
  }

  /**
   * Set the maximum cache message size in bytes.
   *
   * @param maxCacheMessageSizeInBytes - Maximum cache size in bytes
   * @return This builder instance
   * @throws Error if maxCacheMessageSizeInBytes is not positive
   */
  setMaxCacheMessageSizeInBytes(maxCacheMessageSizeInBytes: number): LitePushConsumerBuilder {
    if (maxCacheMessageSizeInBytes <= 0) {
      throw new Error('maxCacheMessageSizeInBytes should be positive');
    }
    this.options.maxCacheMessageSizeInBytes = maxCacheMessageSizeInBytes;
    return this;
  }

  /**
   * Set the consumption thread count.
   *
   * @param consumptionThreadCount - Number of threads for consumption
   * @return This builder instance
   * @throws Error if consumptionThreadCount is not positive
   */
  setConsumptionThreadCount(consumptionThreadCount: number): LitePushConsumerBuilder {
    if (consumptionThreadCount <= 0) {
      throw new Error('consumptionThreadCount should be positive');
    }
    // Note: Node.js uses a single-threaded event loop model, so this configuration
    // has no effect on actual consumption concurrency. It is retained solely for
    // API compatibility with the other clients. Consumption concurrency in Node.js
    // is naturally managed by the event loop and Promise-based async scheduling.
    this.options.consumptionThreadCount = consumptionThreadCount;
    return this;
  }

  /**
   * Set enable fifo consume accelerator.
   *
   * <p>If enabled, messages with different messageGroups are consumed in parallel
   * while messages within the same messageGroup are consumed sequentially.</p>
   *
   * @param enableFifoConsumeAccelerator - Whether to enable FIFO consume accelerator
   * @return This builder instance
   */
  setEnableFifoConsumeAccelerator(enableFifoConsumeAccelerator: boolean): LitePushConsumerBuilder {
    this.options.enableFifoConsumeAccelerator = enableFifoConsumeAccelerator;
    return this;
  }

  /**
   * Finalize the build of LitePushConsumer and start.
   *
   * <p>This method will block until the push consumer starts successfully.
   *
   * <p>Especially, if this method is invoked more than once, different push consumers will be created and started.
   *
   * @return Promise resolving to started LitePushConsumer instance
   * @throws Error if required parameters are not set
   */
  async build(): Promise<LitePushConsumer> {
    if (!this.options.endpoints) {
      throw new Error('clientConfiguration has not been set yet');
    }
    if (!this.options.consumerGroup) {
      throw new Error('consumerGroup has not been set yet');
    }
    if (!this.options.messageListener) {
      throw new Error('messageListener has not been set yet');
    }
    if (!this.options.bindTopic) {
      throw new Error('bindTopic has not been set yet');
    }

    // Build LitePushConsumerImpl
    const options: any = {
      endpoints: this.options.endpoints,
      namespace: this.options.namespace ?? '',
      consumerGroup: this.options.consumerGroup,
      bindTopic: this.options.bindTopic,
      messageListener: this.options.messageListener,
      sslEnabled: this.options.sslEnabled,
      sessionCredentials: this.options.sessionCredentials,
      requestTimeout: this.options.requestTimeout,
      logger: this.options.logger,
      maxCacheMessageCount: this.options.maxCacheMessageCount,
      maxCacheMessageSizeInBytes: this.options.maxCacheMessageSizeInBytes,
      consumptionThreadCount: this.options.consumptionThreadCount,
      enableFifoConsumeAccelerator: this.options.enableFifoConsumeAccelerator,
    };

    const litePushConsumer = new LitePushConsumerImpl(options);
    await litePushConsumer.startup();
    return litePushConsumer;
  }
}
