# parses Terraform d.tfstate files

require "inspec/objects/control"
require "inspec/objects/ruby_helper"
require "inspec/objects/describe"

require "inspec-iggy/file_helper"
require "inspec-iggy/inspec_helper"

module InspecPlugins::Iggy::Terraform
  class Generate
    # parse through the JSON and generate InSpec controls
    def self.parse_generate(tf_file, resource_path, platform)
      # parse the tfstate file to get the Terraform resources
      tfstate = InspecPlugins::Iggy::FileHelper.parse_json(tf_file)
      absolutename = File.absolute_path(tf_file)

      # take those Terraform resources and map to InSpec resources by name and keep all attributes
      # resources -> [{name1 -> {unfiltered_attributes}, name2 -> {unfiltered_attributes}]
      parsed_resources = parse_resources(tfstate, resource_path, platform)

      # InSpec controls generated from matched_resources and attributes
      generated_controls = parse_controls(parsed_resources, absolutename, platform)

      Inspec::Log.debug "Iggy::Terraform::Generate.parse_generate generated_controls = #{generated_controls}"
      generated_controls
    end

    # returns the list of all InSpec resources found in the tfstate file
    def self.parse_resources(tfstate, resource_path, _platform)
      # iterate over the resources
      resources = {}
      tf_resources = tfstate["resources"]
      tf_resources.each do |tf_res|
        resource_type = tf_res["type"]
        next if resource_type.eql?("random_id") # this is a Terraform resource, not a provider resource

        # load resource pack resources
        InspecPlugins::Iggy::InspecHelper.load_resource_pack(resource_path) if resource_path

        # add translation layer
        if InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES.key?(resource_type)
          Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} #{InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]} TRANSLATED"
          resource_type = InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]
        end
        resources[resource_type] = {} if resources[resource_type].nil?
        # does this match an InSpec resource?
        if InspecPlugins::Iggy::InspecHelper.available_resources.include?(resource_type)
          Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} MATCHED"
          tf_res["instances"].each do |instance|
            resource_id = instance["attributes"]["id"]
            resource_attributes = instance["attributes"]
            resources[resource_type][resource_id] = resource_attributes
          end
        else
          Inspec::Log.debug "Iggy::Terraform.Generate.parse_generate resource_type = #{resource_type} SKIPPED"
        end
      end
      resources
    end

    # take the resources and map to describes
    def self.parse_controls(resources, absolutename, platform) # rubocop:disable Metrics/AbcSize
      controls = []
      # iterate over the resources types and their ids
      resources.keys.each do |resource_type|
        resources[resource_type].keys.each do |resource_id|
          # insert new control based off the resource's ID
          ctrl = Inspec::Control.new
          ctrl.id = "#{resource_type}::#{resource_id}"
          ctrl.title = "InSpec-Iggy #{resource_type}::#{resource_id}"
          ctrl.descriptions[:default] = "#{resource_type}::#{resource_id} from the source file #{absolutename}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
          ctrl.impact = "1.0"

          describe = Inspec::Describe.new
          case platform # this may need to get refactored away once Azure is tested
          when "aws"
            qualifier = [resource_type, {}]
            if InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform).key?(resource_type) # there are additional qualifiers
              first = true
              InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform)[resource_type].each do |parameter|
                Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} qualifier found = #{parameter} MATCHED"
                if first # this is the id for the resource
                  value = resources[resource_type][resource_id]["id"] # pull value out of the tf attributes
                  first = false
                else
                  value = resources[resource_type][resource_id][parameter.to_s] # pull value out of the tf attributes
                end
                qualifier[1][parameter] = value
              end
            end
            describe.qualifier.push(qualifier)
          when "azure" # rubocop:disable Lint/EmptyWhen
            # this is a hack for azure, we need a better longterm solution
            # if resource.start_with?('azure_')
            #   name = resource_id.split('/').last
            # else
            #   name = resource_id
            # end

            # if resource_type.start_with?('azure_')
            # if resource_type.eql?('azure_resource_group')
            #   describe.qualifier.push([resource_type, name: name])
            # else
            #   resource_group = resource_id.split('resourceGroups/').last.split('/').first
            #   describe.qualifier.push([resource_type, name: name, group_name: resource_group])
            # end
          when "gcp"
            qualifier = [resource_type, {}]
            if InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform).key?(resource_type)
              InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform)[resource_type].each do |parameter|
                Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} qualifier found = #{parameter} MATCHED"
                value = resources[resource_type][resource_id][parameter.to_s] # pull value out of the tf attributes
                qualifier[1][parameter] = value
              end
            end
            describe.qualifier.push(qualifier)
          end

          # ensure the resource exists unless Azure, which currently doesn't support it as of InSpec 2.2
          describe.add_test(nil, "exist", nil) unless resource_type.start_with?("azure_")

          # if there's a match, see if there are matching InSpec properties
          inspec_properties = InspecPlugins::Iggy::InspecHelper.resource_properties(resource_type, platform)
          # push stuff back into inspec_properties?
          resources[resource_type][resource_id].keys.each do |attr|
            if inspec_properties.member?(attr)
              Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} MATCHED"
              value = resources[resource_type][resource_id][attr]
              if value
                # check to see if there is a translate for this attr
                property = InspecPlugins::Iggy::InspecHelper.translated_resource_property(platform, resource_type, attr)
                describe.add_test(property, "cmp", value)
              else
                Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} SKIPPED FOR NIL"
              end
            else
              Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} SKIPPED"
            end
          end

          ctrl.add_test(describe)
          controls.push(ctrl)
        end
      end
      Inspec::Log.debug "Iggy::Terraform::Generate.parse_generate controls = #{controls}"
      controls
    end
  end
end
