require 'csv'
require 'yaml'
require 'digest'

require_relative '../utilities/inspec_util'
require_relative '../utilities/cci_xml'
require_relative '../utilities/mapping_validator'

module InspecTools
  # Methods for converting from CSV to various formats
  class CSVTool
    def initialize(csv, mapping, name, verbose = false)
      @name = name
      @csv = csv
      @mapping = Utils::MappingValidator.validate(mapping)
      @verbose = verbose
      @csv.shift if @mapping['skip_csv_header']
    end

    def to_inspec(control_name_prefix: nil)
      @controls = []
      @profile = {}
      @cci_xml = Utils::CciXml.get_cci_list('U_CCI_List.xml')
      insert_metadata
      parse_controls(control_name_prefix)
      @profile['controls'] = @controls
      @profile['sha256'] = Digest::SHA256.hexdigest(@profile.to_s)
      @profile
    end

    private

    def insert_metadata
      @profile['name'] = @name
      @profile['title'] = 'InSpec Profile'
      @profile['maintainer'] = 'The Authors'
      @profile['copyright'] = 'The Authors'
      @profile['copyright_email'] = 'you@example.com'
      @profile['license'] = 'Apache-2.0'
      @profile['summary'] = 'An InSpec Compliance Profile'
      @profile['version'] = '0.1.0'
      @profile['supports'] = []
      @profile['attributes'] = []
      @profile['generator'] = {
        name: 'inspec_tools',
        version: VERSION
      }
    end

    def get_nist_reference(cci_number)
      item_node = @cci_xml.xpath("//cci_list/cci_items/cci_item[@id='#{cci_number}']")[0] unless @cci_xml.nil?
      return nil if item_node.nil?

      [] << item_node.xpath('./references/reference[not(@version <= preceding-sibling::reference/@version) and not(@version <=following-sibling::reference/@version)]/@index').text
    end

    def get_cci_number(cell)
      # Return nil if a mapping to the CCI was not provided or if there is not content in the CSV cell.
      return nil if cell.nil? || @mapping['control.tags']['cci'].nil?

      # If the content has been exported from STIG Viewer, the cell will have extra information
      cell.split("\n").first
    end

    def parse_controls(prefix)
      @csv.each do |row|
        control = {}
        control['id']     = generate_control_id(prefix, row[@mapping['control.id']]) unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
        control['title']  = row[@mapping['control.title']] unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
        control['desc']   = row[@mapping['control.desc']] unless @mapping['control.desc'].nil? || row[@mapping['control.desc']].nil?
        control['tags'] = {}
        cci_number = get_cci_number(row[@mapping['control.tags']['cci']])
        nist = get_nist_reference(cci_number) unless cci_number.nil?
        control['tags']['nist'] = nist unless nist.nil? || nist.include?(nil)
        @mapping['control.tags'].each do |tag|
          if tag.first == 'cci'
            if cci_number.is_a? Array
              control['tags'][tag.first] = cci_number
            else
              control['tags'][tag.first] = [cci_number]
            end
            next
          end
          control['tags'][tag.first] = row[tag.last] unless row[tag.last].nil?
        end
        unless @mapping['control.tags']['severity'].nil? || row[@mapping['control.tags']['severity']].nil?
          control['impact'] = Utils::InspecUtil.get_impact(row[@mapping['control.tags']['severity']])
          control['tags']['severity'] = Utils::InspecUtil.get_impact_string(control['impact'])
        end
        @controls << control
      end
    end

    def generate_control_id(prefix, id)
      return id if prefix.nil?

      "#{prefix}-#{id}"
    end
  end
end