///////////////////////////////////////////////////////////////////////////////
//
// (C) 2025 ICE TEA GROUP LLC - ALL RIGHTS RESERVED
//
// 
//
// ALL INFORMATION CONTAINED HEREIN IS, AND REMAINS
// THE PROPERTY OF ICE TEA GROUP LLC AND ITS SUPPLIERS, IF ANY.
// THE INTELLECTUAL PROPERTY AND TECHNICAL CONCEPTS CONTAINED
// HEREIN ARE PROPRIETARY TO ICE TEA GROUP LLC AND ITS SUPPLIERS
// AND MAY BE COVERED BY U.S. AND FOREIGN PATENTS, PATENT IN PROCESS, AND
// ARE PROTECTED BY TRADE SECRET OR COPYRIGHT LAW.
//
// DISSEMINATION OF THIS INFORMATION OR REPRODUCTION OF THIS MATERIAL
// IS STRICTLY FORBIDDEN UNLESS PRIOR WRITTEN PERMISSION IS OBTAINED
// FROM ICE TEA GROUP LLC.
//
///////////////////////////////////////////////////////////////////////////////

using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Wisej.AI.Helpers;

namespace Wisej.AI.Services
{
	/// <summary>
	/// Represents a service for reranking text inputs using the Pinecone API.
	/// </summary>
	/// <remarks>
	/// This service communicates with the Pinecone API to rerank text inputs based on a specified query.
	/// It allows configuration of the API endpoint, API key, model, API version, and the number of top documents to return.
	/// </remarks>
	[ApiCategory("Services/IRerankingService")]
	public class PineconeRerankingService : IRerankingService
	{
		/// <summary>
		/// Gets or sets the URL of the Pinecone service.
		/// </summary>
		[DefaultValue("https://api.pinecone.io/rerank")]
		public virtual string URL
		{
			get;
			set;
		} = "https://api.pinecone.io/rerank";

		/// <summary>
		/// Gets or sets the API key for the Pinecone service.
		/// </summary>
		[DefaultValue(null)]
		public virtual string ApiKey
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the reranking model for the Pinecone service.
		/// </summary>
		[DefaultValue("pinecone-rerank-v0")]
		public virtual string Model
		{
			get;
			set;
		} = "pinecone-rerank-v0";

		/// <summary>
		/// Gets or sets the API version used for requests.
		/// </summary>
		[DefaultValue("2025-04")]
		public virtual string ApiVersion
		{
			get;
			set;
		} = "2025-04";

		/// <summary>
		/// Gets or sets the maximum number of documents to return in the reranked response.
		/// If set to 0 it will return all the documents in the input.
		/// </summary>
		[DefaultValue(10)]
		public virtual int TopN
		{
			get;
			set;
		} = 10;

		/// <summary>
		/// Gets the HTTP client service used to communicate with the Pinecone API.
		/// </summary>
		/// <remarks>
		/// The client is initialized with the API key if it is not already initialized.
		/// </remarks>
		internal virtual IHttpClientService Client
		{
			get
			{
				if (_client == null)
				{
					_client = ServiceHelper.GetService<IHttpClientService>();

					var apiKey = GetApiKey();
					if (!String.IsNullOrEmpty(apiKey))
						_client.SetDefaultHeader("api-key", apiKey);

					_client.SetDefaultHeader("X-Pinecone-API-Version", this.ApiVersion);
				}

				return _client;
			}
		}
		private IHttpClientService _client;

		/// <summary>
		/// Retrieves the API key for the Pinecone service.
		/// </summary>
		/// <returns>The API key for the Pinecone service.</returns>
		protected virtual string GetApiKey()
		{
			return
				!String.IsNullOrEmpty(this.ApiKey)
					? this.ApiKey
					: ApiKeys.GetApiKey(GetType().Name, "RerankingService") ?? ApiKeys.GetApiKey(GetType().Name, "EmbeddingStorageService");
		}

		/// <summary>
		/// Asynchronously reranks the provided chunks based on the specified query.
		/// </summary>
		/// <param name="query">The query used to rerank the chunks.</param>
		/// <param name="chunks">The array of text inputs to be reranked.</param>
		/// <returns>A task that represents the asynchronous operation. The task result contains an array of reranked text inputs.</returns>
		public virtual async Task<string[]> RerankAsync(string query, string[] chunks)
		{
			if (String.IsNullOrEmpty(query))
				return chunks;

			if (chunks == null || chunks.Length == 1)
				return chunks;

			var result = await PostAsync(new
			{
				model = this.Model,
				query = query,
				documents = chunks.Select(d => new { text = d }).ToArray(),
				top_n = this.TopN == 0 ? null : (object)this.TopN,
				return_documents = false
			});

			var data = result.data as dynamic[];
			if (data != null && data.Length > 0)
			{
				chunks = data.Select(d => chunks[(int)d.index]).ToArray();
			}

			return chunks;
		}

		#region Implementation

		private async Task<dynamic> PostAsync(object data)
		{
			var url = this.URL;
			var client = this.Client;

			var payload = JSON.Stringify(data, JSON.SerializerOptions.None);
			var content = new StringContent(payload ?? "", Encoding.UTF8, "application/json");

			var response = await client.PostAsync(url, content);

			if (!response.IsSuccessStatusCode)
			{
				var reader = new StreamReader(await response.Content.ReadAsStreamAsync());
				throw new Exception(reader.ReadToEnd());
			}

			response.EnsureSuccessStatusCode();
			var stream = await response.Content.ReadAsStreamAsync();
			return JSON.Parse(stream);
		}
		#endregion
	}
}
