using NUnit.Framework;
using OpenAI.Assistants;
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace OpenAI.Examples;

public partial class AssistantExamples
{
    [Test]
    public async Task Example02b_FunctionCallingStreaming()
    {
        // This example parallels the content at the following location:
        // https://platform.openai.com/docs/assistants/tools/function-calling/function-calling-beta
        #region Step 1 - Define Functions

        // First, define the functions that the assistant will use in its defined tools.

        FunctionToolDefinition getTemperatureTool = new("get_current_temperature")
        {
            Description = "Gets the current temperature at a specific location.",
            Parameters = BinaryData.FromString("""
            {
              "type": "object",
              "properties": {
                "location": {
                  "type": "string",
                  "description": "The city and state, e.g., San Francisco, CA"
                },
                "unit": {
                  "type": "string",
                  "enum": ["Celsius", "Fahrenheit"],
                  "description": "The temperature unit to use. Infer this from the user's location."
                }
              }
            }
            """),
        };

        FunctionToolDefinition getRainProbabilityTool = new("get_current_rain_probability")
        {
            Description = "Gets the current forecasted probability of rain at a specific location,"
                + " represented as a percent chance in the range of 0 to 100.",
            Parameters = BinaryData.FromString("""
            {
              "type": "object",
              "properties": {
                "location": {
                  "type": "string",
                  "description": "The city and state, e.g., San Francisco, CA"
                }
              },
              "required": ["location"]
            }
            """),
        };

        #endregion

        // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning.
        AssistantClient client = new(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

        #region Create a new assistant with function tools
        // Create an assistant that can call the function tools.
        AssistantCreationOptions assistantOptions = new()
        {
            Name = "Example: Function Calling",
            Instructions =
                "Don't make assumptions about what values to plug into functions."
                + " Ask for clarification if a user request is ambiguous.",
            Tools = { getTemperatureTool, getRainProbabilityTool },
        };

        Assistant assistant = await client.CreateAssistantAsync("gpt-4-turbo", assistantOptions);
        #endregion

        #region Step 2 - Create a thread and add messages
        AssistantThread thread = await client.CreateThreadAsync();
        ThreadMessage message = await client.CreateMessageAsync(
            thread.Id,
            MessageRole.User,
            [
                "What's the weather in San Francisco today and the likelihood it'll rain?"
            ]);
        #endregion

        #region Step 3 - Initiate a streaming run
        AsyncCollectionResult<StreamingUpdate> asyncUpdates
            = client.CreateRunStreamingAsync(thread.Id, assistant.Id);

        ThreadRun currentRun = null;
        do
        {
            currentRun = null;
            List<ToolOutput> outputsToSubmit = [];
            await foreach (StreamingUpdate update in asyncUpdates)
            {
                if (update is RequiredActionUpdate requiredActionUpdate)
                {
                    if (requiredActionUpdate.FunctionName == getTemperatureTool.FunctionName)
                    {
                        outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "57"));
                    }
                    else if (requiredActionUpdate.FunctionName == getRainProbabilityTool.FunctionName)
                    {
                        outputsToSubmit.Add(new ToolOutput(requiredActionUpdate.ToolCallId, "25%"));
                    }
                }
                else if (update is RunUpdate runUpdate)
                {
                    currentRun = runUpdate;
                }
                else if (update is MessageContentUpdate contentUpdate)
                {
                    Console.Write(contentUpdate.Text);
                }
            }
            if (outputsToSubmit.Count > 0)
            {
                asyncUpdates = client.SubmitToolOutputsToRunStreamingAsync(currentRun.ThreadId, currentRun.Id, outputsToSubmit);
            }
        }
        while (currentRun?.Status.IsTerminal == false);

        #endregion

        // Optionally, delete the resources for tidiness if no longer needed.
        RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow };
        _ = await client.DeleteThreadAsync(thread.Id, noThrowOptions);
        _ = await client.DeleteAssistantAsync(assistant.Id, noThrowOptions);
    }
}
