using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;

using AIRuntime.Runtime.Models;
using AIRuntime.Runtime.Providers.Models;

using UnityEngine;


namespace AIRuntime.Runtime.Providers.Ollama
{
    /// <summary>
    /// Ollama implementation of the AIRuntime provider interface.
    /// Converts provider tools into Ollama tool definitions.
    /// </summary>
    public sealed class OllamaProvider : IProvider, IProviderInputCapabilities
    {
        private readonly IOllamaClient _client;

        public string Model => _client.Model;
        public bool SupportsImageInput { get; set; }


        public string Name =>
            "Ollama";


        public OllamaProvider(
            string model = "qwen2.5:7b-instruct")
            : this(
                new OllamaClient(
                    model: model))
        {
        }


        public OllamaProvider(
            IOllamaClient client)
        {
            _client =
                client ??
                throw new ArgumentNullException(
                    nameof(client));


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


        public async Task<ChatCompletion> ChatAsync(
            IReadOnlyList<ConversationMessage> messages,
            IReadOnlyList<ProviderTool> tools,
            CancellationToken cancellationToken = default)
        {
            if (messages == null)
            {
                throw new ArgumentNullException(
                    nameof(messages));
            }


            if (tools == null)
            {
                throw new ArgumentNullException(
                    nameof(tools));
            }


            cancellationToken.ThrowIfCancellationRequested();

            ProviderInputGuard.Validate(this, messages);


            ChatRequest request =
                new ChatRequest
                {
                    model =
                        _client.Model,

                    stream =
                        ProviderStreamingContext.IsActive
                };


            //--------------------------------------------------
            // Conversation Messages
            //--------------------------------------------------

            foreach (ConversationMessage message in messages)
            {
                cancellationToken.ThrowIfCancellationRequested();


                MessageDto dto =
                    new MessageDto
                    {
                        role =
                            message.Role,

                        content =
                            message.Content,

                        tool_call_id =
                            message.ToolCallId,

                        name =
                            message.Name
                    };

                if (message.Attachments != null)
                {
                    dto.images = new List<string>();
                    foreach (ConversationAttachment attachment in message.Attachments)
                    {
                        if (attachment == null || string.IsNullOrWhiteSpace(attachment.Path) ||
                            string.IsNullOrWhiteSpace(attachment.MediaType) ||
                            !attachment.MediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
                            continue;
                        string fullPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory,
                            attachment.Path.Replace('/', Path.DirectorySeparatorChar)));
                        if (!File.Exists(fullPath)) continue;
                        FileInfo info = new FileInfo(fullPath);
                        if (info.Length <= 0 || info.Length > 20L * 1024L * 1024L) continue;
                        dto.images.Add(Convert.ToBase64String(File.ReadAllBytes(fullPath)));
                    }
                    if (dto.images.Count == 0) dto.images = null;
                }


                if (message.ToolCalls != null)
                {
                    dto.tool_calls =
                        new List<ToolCallDto>();


                    foreach (ToolCall call in message.ToolCalls)
                    {
                        cancellationToken.ThrowIfCancellationRequested();


                        dto.tool_calls.Add(
                            new ToolCallDto
                            {
                                id =
                                    call.Id,

                                type =
                                    "function",

                                function =
                                    new FunctionCallDto
                                    {
                                        name =
                                            call.Name,

                                        arguments =
                                            call.Arguments
                                    }
                            });
                    }
                }


                request.messages.Add(
                    dto);
            }


            //--------------------------------------------------
            // Provider-facing Tool Definitions
            //--------------------------------------------------

            foreach (ProviderTool tool in tools)
            {
                cancellationToken.ThrowIfCancellationRequested();


                request.tools.Add(
                    new ToolDto
                    {
                        type =
                            "function",

                        function =
                            new FunctionDto
                            {
                                name =
                                    tool.Function.Name,

                                description =
                                    tool.Function.Description,

                                parameters =
                                    tool.Function.Parameters
                            }
                    });
            }


            //--------------------------------------------------
            // Serialize Request
            //--------------------------------------------------

            string json =
                JsonConvert.SerializeObject(
                    request,
                    Formatting.Indented,
                    new JsonSerializerSettings
                    {
                        ContractResolver =
                            new CamelCasePropertyNamesContractResolver()
                    });
            // The transport diagnostic journal records payloads without duplicating them in Unity Console.



            cancellationToken.ThrowIfCancellationRequested();


            //--------------------------------------------------
            // Send Request
            //--------------------------------------------------

            string responseJson =
                await _client.ChatAsync(
                    json,
                    cancellationToken);


            cancellationToken.ThrowIfCancellationRequested();
            // The transport diagnostic journal records payloads without duplicating them in Unity Console.



            if (ProviderStreamingContext.IsActive)
            {
                responseJson =
                    ProviderStreamProtocol
                        .ConvertToCompletionJson(
                            responseJson, Name);
            }


            //--------------------------------------------------
            // Deserialize Response
            //--------------------------------------------------

            ChatResponse response =
                JsonConvert.DeserializeObject<ChatResponse>(
                    responseJson);


            cancellationToken.ThrowIfCancellationRequested();


            return ConvertResponse(
                response);
        }


        private ChatCompletion ConvertResponse(
            ChatResponse response)
        {
            if (response == null)
            {
                throw new Exception(
                    "Ollama returned a null response.");
            }


            if (response.choices == null ||
                response.choices.Count == 0)
            {
                throw new Exception(
                    "Ollama response contained no choices.");
            }


            MessageDto message =
                response.choices[0].message;


            if (message == null)
            {
                throw new Exception(
                    "Ollama response contained no assistant message.");
            }


            ChatCompletion completion =
                new ChatCompletion
                {
                    Content =
                        message.content ??
                        string.Empty
                };


            if (message.tool_calls != null)
            {
                for (int index = 0;
                     index < message.tool_calls.Count;
                     index++)
                {
                    ToolCallDto call =
                        message.tool_calls[index];


                    ValidateToolCall(
                        call,
                        index);


                    completion.ToolCalls.Add(
                        new ToolCall
                        {
                            Id =
                                call.id,

                            Name =
                                call.function.name,

                            Arguments =
                                call.function.arguments
                        });
                }
            }


            return completion;
        }


        private static void ValidateToolCall(
            ToolCallDto toolCall,
            int index)
        {
            if (toolCall == null)
            {
                throw InvalidToolCall(
                    index,
                    "the entry was null");
            }


            if (toolCall.function == null)
            {
                throw InvalidToolCall(
                    index,
                    "the function definition was missing");
            }


            if (string.IsNullOrWhiteSpace(toolCall.id))
            {
                throw InvalidToolCall(
                    index,
                    "the call ID was missing");
            }


            if (string.IsNullOrWhiteSpace(
                    toolCall.function.name))
            {
                throw InvalidToolCall(
                    index,
                    "the function name was missing");
            }


            if (string.IsNullOrWhiteSpace(
                    toolCall.function.arguments))
            {
                throw InvalidToolCall(
                    index,
                    "the function arguments were missing");
            }


            try
            {
                JObject.Parse(
                    toolCall.function.arguments);
            }
            catch (JsonException)
            {
                throw InvalidToolCall(
                    index,
                    "the function arguments were not a JSON object");
            }
        }


        private static InvalidOperationException InvalidToolCall(
            int index,
            string reason)
        {
            return new InvalidOperationException(
                $"Ollama returned an invalid response: tool call at index {index} was malformed because {reason}.");
        }
    }
}
