require 'tty-pager'

########################################################################
# GPSCommands provides command line GPS related tooling
# +query+ for GPS nodes
########################################################################
module GeoCLI::GPSCommands
  class QueryError < StandardError; end

  QUERY_SYNTAX = %r{
    (?<project>[a-zA-Z0-9\-_/*]+):         # Match the project name (required)
    (?<environment>[a-zA-Z0-9\-_*]+):      # Match the environment (required)
    (?<configuration>[a-zA-Z0-9\-_*]+):    # Match the configuration (required)
    (?<node_type>[a-zA-Z0-9\-_*]+):        # Match the node_type (required)
    (?<node_name>[a-zA-Z0-9\-_/*.]+)       # Match the node_name (required)
  }x

  def format_nodes(nodes, expanded = false, attributes_to_show = nil)
    nodes.map do |node|
      attributes = expanded ? node.attributes : node.initial_attributes

      unless attributes_to_show.nil?
        attributes = attributes.select { |name| attributes_to_show.include?(name) }
      end

      output = if attributes.empty?
                 "N/A"
               else
                 strip_leading_line(attributes.to_yaml)
               end

      ">> #{node.node_id}\n#{output}"
    end.join("\n\n") + "\n"
  end

  def strip_leading_line(yaml)
    yaml.split("\n")[1..-1].join("\n")
  end

  def validate_query!(query)
    raise QueryError, "#{query} does not match the query syntax, please try again" unless query.match?(QUERY_SYNTAX)
  end

  def return_results(nodes, output_path = nil)
    if output_path.nil?
      TTY::Pager.new.page(nodes)
    else
      File.write(output_path, nodes)
    end
  end

  def query_cmd
    command :query do |c|
      c.syntax = './geo query <query> --attributes ATTRIBUTES --expanded'
      c.description = 'Query the GPS graph'
      c.option '--attributes ATTRIBUTES', Array, 'List of attributes to show for each matching node'
      c.option '--expanded', 'If true, display the attributes with defaults inserted'
      c.option '--out PATH', String, 'File path to store output'

      c.action do |args, options|
        options.default({ environment: "development", expanded: false })
        require_environment(options)

        query = args.first
        validate_query!(query)
        matching_nodes = gps.where(query)

        return "No matching nodes found for query: #{query}" if matching_nodes.empty?
        say "Found #{matching_nodes.length} nodes..."

        return_results(
          format_nodes(matching_nodes, options.expanded, options.attributes),
          options.out
        )
      end
    end
  end

  # rubocop:disable Metrics/MethodLength
  def export_graph_command
    command :'export-graph' do |c|
      c.syntax = './geo export-graph'
      c.summary = 'Exports the GPS graph to a file.'
      c.description = <<~HEREDOC
        This graph has the entirety of all the attributes in a GPS node filled out.
        Imagine a GPS node named service that had two options, foo: bool, and bar: bool default true.
        If you had a GPS file like this:
        development:
          api:
            service:
              main:
                foo: false
        This command would output:
        {
          "my-project": {
            "development": {
              "api": {
                "service": {
                  "main": {
                    "foo": False,
                    "bar": True
                  }
                }
              }
            }
          }
        }
         This is useful for getting the entire state of all of your infrastructure at this point in time.
      HEREDOC
      c.option '--stdout', 'Prints output to terminal instead of writing to graph.json'
      c.option '--file FILENAME', String, 'Exports the file to the given path instead of graph.json'

      c.action do |args, options|
        # We have to stub commands because gps/nodes/postgres.rb tests for new resources in setup
        GeoCLI::TestCmdStubs.stub!
        options.default({ stdout: false, file: './graph.json' })
        require_environment(options)

        graph = GeoCLI.instance.gps.expanded_hash.to_json

        if options.stdout
          puts graph
        else
          File.write(options.file, graph)
        end
      end
    end
  end

  def diff_graph_command
    command :'diff-graph' do |c|
      c.syntax = './geo diff-graph FILE1 FILE2'
      c.summary = 'Exports the difference of two graph files to a file.'
      c.description = <<~HEREDOC
        Graph files should be generated by calling the export-graph command.
        The differences provided show you the keys that changed and the expected end state.
        Imagine the diff between these:
        development:
          api:
            service:
              main:
                port: 80
        and
        development:
          api:
            service:
              main:
                port: 82
         This call would return an array of differences between the two graphs:
        [
          {"action": "~", "key": "my/file.development.api.service.main.port", "value": 82}
        ]
      HEREDOC
      c.option '--stdout', 'Prints output to terminal instead of writing to diff.json'
      c.option '--file FILENAME', String, 'Exports the file to the given path instead of diff.json'

      c.action do |args, options|
        options.default({ stdout: false, file: './diff.json' })
        diff = GeoEngineer::GPS::GraphUtils.difference(
          GeoEngineer::GPS::GraphUtils.flatten(JSON.parse(File.read(args[0]))),
          GeoEngineer::GPS::GraphUtils.flatten(JSON.parse(File.read(args[1])))
        )

        out = diff.to_json
        if options.stdout
          puts out
        else
          File.write(options.file, out)
        end
      end
    end
  end
  # rubocop:enable Metrics/MethodLength
end
