using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using UnityEngine.Networking;


namespace AIRuntime.Runtime.Providers.Ollama
{
    /// <summary>
    /// Low-level HTTP client for Ollama.
    ///
    /// This class knows only about HTTP.
    /// It does not know conversations or capabilities.
    /// </summary>
    public sealed class OllamaClient :
        IOllamaClient
    {
        public string BaseUrl { get; }

        public string Model { get; }


        public OllamaClient(
            string baseUrl = "http://localhost:11434/v1",
            string model = "qwen2.5:7b-instruct")
        {
            if (string.IsNullOrWhiteSpace(baseUrl))
            {
                throw new ArgumentException(
                    "Ollama base URL cannot be empty.",
                    nameof(baseUrl));
            }


            if (string.IsNullOrWhiteSpace(model))
            {
                throw new ArgumentException(
                    "Ollama model cannot be empty.",
                    nameof(model));
            }


            BaseUrl =
                baseUrl.TrimEnd('/');


            Model =
                model;
        }


        /// <summary>
        /// Sends a raw JSON request to Ollama.
        /// </summary>
        public async Task<string> ChatAsync(
            string jsonBody,
            CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(jsonBody))
            {
                throw new ArgumentException(
                    "Ollama request body cannot be empty.",
                    nameof(jsonBody));
            }


            cancellationToken.ThrowIfCancellationRequested();

            string endpoint =
                $"{BaseUrl}/chat/completions";

            bool streaming =
                ProviderStreamingContext.IsActive;

            using IDisposable trace = ProviderTransportDebug.EnsureAttempt("Ollama");
            var elapsed = System.Diagnostics.Stopwatch.StartNew();


            ProviderTransportDebug.Publish(
                "Ollama",
                "REQUEST",
                endpoint,
                jsonBody,
                streaming);


            using UnityWebRequest request =
                new UnityWebRequest(
                    endpoint,
                    UnityWebRequest.kHttpVerbPOST);


            byte[] body =
                Encoding.UTF8.GetBytes(
                    jsonBody);


            request.uploadHandler =
                new UploadHandlerRaw(
                    body);


            ProviderStreamingDownloadHandler streamingHandler =
                streaming
                    ? new ProviderStreamingDownloadHandler(
                        "Ollama",
                        endpoint,
                        ProviderStreamingContext.Capture(),
                        ProviderStreamTerminationPolicy.OpenAICompatible)
                    : null;

            request.downloadHandler =
                streaming
                    ? streamingHandler
                    : new DownloadHandlerBuffer();


            request.SetRequestHeader(
                "Content-Type",
                "application/json");


            bool completedByStreamMarker =
                false;

            ProviderRequestLifetime.Register(
                request);

            try
            {
                UnityWebRequestAsyncOperation operation =
                    request.SendWebRequest();

                while (!operation.isDone)
                {
                    if (streaming &&
                        streamingHandler.IsStreamComplete)
                    {
                        completedByStreamMarker =
                            true;

                        request.Abort();

                        break;
                    }


                    if (cancellationToken.IsCancellationRequested)
                    {
                        request.Abort();

                        cancellationToken
                            .ThrowIfCancellationRequested();
                    }


                    await Task.Yield();
                }


                cancellationToken
                    .ThrowIfCancellationRequested();

                string responseText =
                    streaming
                        ? streamingHandler.RawText
                        : request.downloadHandler.text;

                ProviderTransportDebug.Publish("Ollama", "HTTP RESPONSE", endpoint, responseText, streaming,
                    new Newtonsoft.Json.Linq.JObject {
                        ["StatusCode"] = request.responseCode, ["TransportError"] = request.error,
                        ["FinalUrl"] = request.url, ["ElapsedMilliseconds"] = elapsed.ElapsedMilliseconds,
                        ["Headers"] = ProviderTransportDebug.RedactHeaders(request.GetResponseHeaders()),
                        ["CompletedByStreamMarker"] = completedByStreamMarker
                    });


#if UNITY_2020_1_OR_NEWER

                if (!completedByStreamMarker &&
                    request.result !=
                    UnityWebRequest.Result.Success)

#else

                if (!completedByStreamMarker &&
                    (request.isNetworkError ||
                     request.isHttpError))

#endif
                {
                    throw new ProviderHttpException(
                        "Ollama",
                        request.responseCode,
                        request.error,
                        responseText);
                }


                return responseText;
            }
            catch (OperationCanceledException)
            {
                try
                {
                    request.Abort();
                }
                catch (ObjectDisposedException)
                {
                    // Domain-reload preparation may already have disposed it.
                }

                throw;
            }
            finally
            {
                try
                {
                    ProviderTransportDebug.Publish("Ollama", "TRANSPORT END", endpoint,
                        streaming ? streamingHandler.RawText : request.downloadHandler.text, streaming,
                        new Newtonsoft.Json.Linq.JObject {
                            ["StatusCode"] = request.responseCode, ["TransportError"] = request.error,
                            ["Cancelled"] = cancellationToken.IsCancellationRequested,
                            ["ElapsedMilliseconds"] = elapsed.ElapsedMilliseconds
                        });
                }
                catch (ObjectDisposedException) { }
                ProviderRequestLifetime.Unregister(
                    request);
            }
        }
    }
}
