# frozen_string_literal: false

require 'json'
require 'socket'

module PWN
  module SAST
    # SAST Module used to identify the versions
    # of dependent software within source repos to ensure patching
    # requirements for those dependencies can be met.
    module PomVersion
      # Supported Method Parameters::
      # PWN::SAST::PomVersion.scan(
      #   dir_path: 'optional path to dir defaults to .'
      #   git_repo_root_uri: 'optional http uri of git repo scanned'
      # )

      public_class_method def self.scan(opts = {})
        dir_path = opts[:dir_path]
        git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
        result_arr = []
        ai_introspection = PWN::Env[:ai][:introspection]
        logger_results = "AI Introspection => #{ai_introspection} => "

        PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
          if File.file?(entry) && File.basename(entry) == 'pom.xml' && entry !~ /test/i
            line_no_and_contents_arr = []
            entry_beautified = false

            if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
              js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED 2> /dev/null`.to_s.scrub
              entry = "#{entry}.JS-BEAUTIFIED"
              entry_beautified = true
            end

            test_case_filter = "
              grep -in -B2 \
              -e 'version' {PWN_SAST_SRC_TARGET} 2> /dev/null
            "

            str = `#{test_case_filter}`.to_s.scrub

            if str.to_s.empty?
              # If str length is >= 64 KB do not include results. (Due to Mongo Document Size Restrictions)
              logger_results = "#{logger_results}~" # Catching bugs is good :)
            else
              str = "1:Result larger than 64KB -> Size: #{str.to_s.length}.  Please click the \"Path\" link for more details." if str.to_s.length >= 64_000

              hash_line = {
                timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s,
                security_references: security_references,
                filename: { git_repo_root_uri: git_repo_root_uri, entry: entry },
                line_no_and_contents: '',
                raw_content: str,
                test_case_filter: test_case_filter
              }

              # COMMMENT: Must be a better way to implement this (regex is kinda funky)
              multi_line_contents_split = str.split("\n--\n")
              multi_line_contents_split.each do |this_multi_line_content|
                line_contents_split = this_multi_line_content.split(/^(\d{1,}):|^(\d{1,})-/)[1..-1]
                line_no_count = line_contents_split.length # This should always be an even number
                current_count = 0
                while line_no_count > current_count
                  line_no = line_contents_split[current_count]
                  contents = line_contents_split[current_count + 1]
                  if Dir.exist?('.git')
                    repo_root = '.'

                    author = PWN::Plugins::Git.get_author(
                      repo_root: repo_root,
                      from_line: line_no,
                      to_line: line_no,
                      target_file: entry,
                      entry_beautified: entry_beautified
                    )
                  end
                  author ||= 'N/A'

                  ai_analysis = nil
                  if ai_introspection
                    request = {
                      scm_uri: "#{hash_line[:filename][:git_repo_root_uri]}/#{hash_line[:filename][:entry]}",
                      line_no: line_no,
                      source_code_snippet: contents
                    }.to_json
                    response = PWN::AI::Introspection.reflect(request: request)
                    if response.is_a?(Hash)
                      ai_analysis = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
                      ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
                    end
                  end

                  hash_line[:line_no_and_contents] = line_no_and_contents_arr.push(
                    line_no: line_no,
                    contents: contents,
                    author: author,
                    ai_analysis: ai_analysis
                  )

                  current_count += 2
                end
                hash_line[:line_no_and_contents] = line_no_and_contents_arr.push(
                  line_no: '--',
                  contents: '--',
                  author: '--'
                )
              end
              result_arr.push(hash_line)
              logger_results = "#{logger_results}x" # Seeing progress is good :)
            end
          end
        end
        logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
        if logger_results.empty?
          @@logger.info("#{logger_banner}: No files applicable to this test case.\n")
        else
          @@logger.info("#{logger_banner} => #{logger_results}complete.\n")
        end
        result_arr
      rescue StandardError => e
        raise e
      end

      # Used primarily to map NIST 800-53 Revision 4 Security Controls
      # https://web.nvd.nist.gov/view/800-53/Rev4/impact?impactName=HIGH
      # to PWN Exploit & Static Code Anti-Pattern Matching Modules to
      # Determine the level of Testing Coverage w/ PWN.

      public_class_method def self.security_references
        {
          sast_module: self,
          section: 'VULNERABILITY SCANNING',
          nist_800_53_uri: 'https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=RA-05',
          cwe_id: '.0',
          cwe_uri: 'https://cwe.mitre.org/data/definitions/1104.html'
        }
      rescue StandardError => e
        raise e
      end

      # Author(s):: 0day Inc. <support@0dayinc.com>

      public_class_method def self.authors
        "AUTHOR(S):
          0day Inc. <support@0dayinc.com>
        "
      end

      # Display Usage for this Module

      public_class_method def self.help
        puts "USAGE:
          sast_arr = #{self}.scan(
            dir_path: 'optional path to dir defaults to .',
            git_repo_root_uri: 'optional http uri of git repo scanned'
          )

          #{self}.authors
        "
      end
    end
  end
end
