require 'openai'
class Captain::Agent
  attr_reader :name, :tools, :prompt, :persona, :goal, :secrets

  def initialize(name:, config:)
    @name = name
    @prompt = construct_prompt(config)
    @tools = prepare_tools(config[:tools] || [])
    @messages = config[:messages] || []
    @max_iterations = config[:max_iterations] || 10
    @llm = Captain::LlmService.new(api_key: config[:secrets][:OPENAI_API_KEY])
    @logger = Rails.logger

    @logger.info(@prompt)
  end

  def execute(input, context)
    setup_messages(input, context)
    result = {}
    @max_iterations.times do |iteration|
      push_to_messages(role: 'system', content: 'Provide a final answer') if iteration == @max_iterations - 1

      result = @llm.call(@messages, functions)
      handle_llm_result(result)

      break if result[:stop]
    end

    result[:output]
  end

  def register_tool(tool)
    @tools << tool
  end

  private

  def setup_messages(input, context)
    if @messages.empty?
      push_to_messages({ role: 'system', content: @prompt })
      push_to_messages({ role: 'assistant', content: context }) if context.present?
    end
    push_to_messages({ role: 'user', content: input })
  end

  def handle_llm_result(result)
    if result[:tool_call]
      tool_result = execute_tool(result[:tool_call])
      push_to_messages({ role: 'assistant', content: tool_result })
    else
      push_to_messages({ role: 'assistant', content: result[:output] })
    end
    result[:output]
  end

  def execute_tool(tool_call)
    function_name = tool_call['function']['name']
    arguments = JSON.parse(tool_call['function']['arguments'])

    tool = @tools.find { |t| t.name == function_name }
    tool.execute(arguments, {})
  rescue StandardError => e
    "Tool execution failed: #{e.message}"
  end

  def construct_prompt(config)
    return config[:prompt] if config[:prompt]

    <<~PROMPT
      Persona: #{config[:persona]}
      Objective: #{config[:goal]}

      Guidelines:
      - Persistently work towards achieving the stated objective without deviation.
      - Use only the provided tools to complete the task. Avoid inventing or assuming function names.
      - Set `'stop': true` once the objective is fully achieved.
      - DO NOT return tool usage as the final result.
      - If sufficient information is available to deliver result, compile and present it to the user.
      - Always return a final result and ENSURE the final result is formatted in Markdown.

      Output Structure:

      1. **Tool Usage:**
        - If a relevant function is identified, call it directly without unnecessary explanations.

      2. **Final Answer:**
        When ready to provide a complete response, follow this JSON format:

        ```json
        {
          "thought_process": "Explain the reasoning and steps taken to arrive at the final result.",
          "result": "Provide the complete response in clear, structured text.",
          "stop": true
        }
    PROMPT
  end

  def prepare_tools(tools = [])
    tools.map do |_, tool|
      Captain::Tool.new(
        name: tool['name'],
        config: {
          description: tool['description'],
          properties: tool['properties'],
          secrets: tool['secrets'],
          implementation: tool['implementation']
        }
      )
    end
  end

  def functions
    @tools.map do |tool|
      properties = {}
      tool.properties.each do |property_name, property_details|
        properties[property_name] = {
          type: property_details[:type],
          description: property_details[:description]
        }
      end
      required = tool.properties.select { |_, details| details[:required] == true }.keys
      {
        type: 'function',
        function: {
          name: tool.name,
          description: tool.description,
          parameters: { type: 'object', properties: properties, required: required }
        }
      }
    end
  end

  def push_to_messages(message)
    @logger.info("\n\n\nMessage: #{message}\n\n\n")
    @messages << message
  end
end
