#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup' # Ensure correct gem dependencies
require 'optparse'
require 'multi_json'
require 'actionmcp'
require 'action_mcp/client'
require 'securerandom'
require 'logger'

# Default options
options = {
  logging_level: 'INFO',
  auto_initialize: true
}

# Set up logger
logger = Logger.new($stdout)
logger.formatter = proc do |severity, _, _, msg|
  "#{severity}: #{msg}\n"
end

# Parse command-line arguments
parser = OptionParser.new do |opts|
  opts.banner = 'Usage: mcp_client ENDPOINT [options]'
  opts.separator ''
  opts.separator 'ENDPOINT must be an HTTP(S) URL (e.g., http://localhost:3000/action_mcp)'
  opts.on('-l', '--log-level LEVEL', 'Set log level (DEBUG, INFO, WARN, ERROR)') do |l|
    options[:logging_level] = l.upcase
    logger.level = begin
      Logger.const_get(l.upcase)
    rescue StandardError
      Logger::INFO
    end
  end
  opts.on('--no-auto-init', "Don't automatically initialize the connection") do
    options[:auto_initialize] = false
  end
  opts.on('-h', '--help', 'Show this help message') do
    puts opts
    exit
  end
end

# Extract first argument as endpoint
endpoint = ARGV.shift

# Parse remaining options
parser.parse!(ARGV)

if endpoint.nil?
  puts 'Error: You must provide an MCP endpoint.'
  puts parser
  exit 1
end

unless endpoint =~ %r{\Ahttps?://}
  puts 'Error: Only HTTP(S) endpoints are supported. STDIO/command endpoints are not allowed.'
  exit 1
end

# Function to generate a unique request ID
def generate_request_id
  SecureRandom.uuid
end

# Function to parse command shortcuts and return a Request object
def parse_command(input)
  parts = input.strip.split(/\s+/)
  command = parts.shift

  case command
  when 'call_tool'
    tool_name = parts.shift
    return nil unless tool_name

    arguments = {}
    parts.each do |arg|
      key, value = arg.split(':', 2)
      next unless value

      # Try to convert the value to appropriate type
      parsed_value = case value
      when /^\d+$/
                       value.to_i
      when /^\d+\.\d+$/
                       value.to_f
      when 'true'
                       true
      when 'false'
                       false
      when 'null'
                       nil
      else
                       value
      end

      arguments[key] = parsed_value
    end

    JSON_RPC::Request.new(
      id: generate_request_id,
      method: 'tools/get',
      params: {
        'name' => tool_name,
        'arguments' => arguments
      }
    )
  when 'list_tools'
    JSON_RPC::Request.new(
      id: generate_request_id,
      method: 'tools/list'
    )
  when 'list_prompts'
    JSON_RPC::Request.new(
      id: generate_request_id,
      method: 'prompts/list'
    )
  end
end

# Help message for shortcuts
def print_help
  puts 'Available shortcuts:'
  puts '  list_tools'
  puts '    - Get a list of available tools'
  puts '  call_tool TOOL_NAME PARAM1:VALUE1 PARAM2:VALUE2 ...'
  puts '    - Sends a tools/get request with the specified tool and parameters'
  puts '  list_prompts'
  puts '    - Get a list of available prompts'
  puts '  get_prompt PROMPT_NAME PARAM1:VALUE1 PARAM2:VALUE2 ...'
  puts '    - Sends a prompts/get request with the specified prompt and arguments'
  puts '  help - Show this help message'
  puts '  exit - Quit the client'
  puts 'Otherwise, enter a raw JSON-RPC request to send directly'
end

# Initialize and start the client (only HTTP(S) endpoints are supported)
client = ActionMCP.create_client(endpoint, logger: logger)

# Start the transport
unless client.connect
  error_msg = client.connection_error || 'Unknown connection error'
  puts "\nERROR: Failed to connect to MCP server at #{endpoint}"
  puts "Reason: #{error_msg}"
  puts "\nPlease check that:"
  puts '  1. The server is running'
  puts '  2. The endpoint URL/address is correct'
  puts '  3. Any required firewall ports are open'

  if endpoint =~ %r{\Ahttps?://}
    puts '  4. The URL includes the correct protocol, host, and port'
    puts '     For example: http://localhost:3000/action_mcp'
  end

  exit 1
end

Signal.trap('INT') do
  puts "\nReceived Ctrl+C. Disconnecting..."
  client.disconnect
  puts 'MCP Client stopped.'
  exit 0
end

# Main REPL loop
loop do
  print 'mcp> '
  input = gets&.chomp
  break unless input # Handle EOF
  next if input.empty?

  case input.downcase
  when 'exit'
    break
  when 'help'
    print_help
    next
  else
    begin
      # Check if input is a command shortcut
      if input.start_with?('call_tool')
        request = parse_command(input)
        logger.debug("Parsed shortcut to: #{request.to_h}") if request
      elsif input.start_with?('connect') || input.start_with?('initialize')
        request = parse_command(input)
        logger.debug("Initializing connection with: #{request.to_h}") if request
      elsif input.start_with?('list_tools') || input.start_with?('list_prompts')
        request = parse_command(input)
        logger.debug("Requesting tool list: #{request.to_h}") if request
      else
        # Try parsing as JSON and creating a Request object
        begin
          json = MultiJson.load(input)
          # Validate that the parsed JSON has the required fields
          if json['method']
            request = JSON_RPC::Request.new(
              id: json['id'] || generate_request_id,
              method: json['method'],
              params: json['params']
            )
          else
            puts "Invalid JSON-RPC request: missing 'method' field"
            next
          end
        rescue MultiJson::ParseError => e
          puts "Invalid input: not a valid command or JSON. #{e.message}"
          next
        rescue JSON_RPC::JsonRpcError => e
          puts "Invalid JSON-RPC request: #{e.message}"
          next
        end
      end

      if request
        client.send_request(request.to_h)
      else
        puts "Invalid command format. Type 'help' for available commands."
      end
    rescue StandardError => e
      puts "Error: #{e.message}"
      puts e.backtrace.first(5) if logger.level == Logger::DEBUG
    end
  end
end

puts 'Disconnecting...'
client.disconnect
puts 'MCP Client stopped.'
