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

require "shellwords"
require "json"
require "jira_ref_parser"
require "yaml"

GERGICH_GIT_PATH = ENV.fetch("GERGICH_GIT_PATH", ".")

def git(command)
  Dir.chdir(GERGICH_GIT_PATH) do
    `git #{command}`
  end
end

# rubocop:disable Style/GlobalVars -- yeah a global, I'm being lazy and refactoring to an object
def comment(severity, line, message, link_to_guidelines: false)
  if ENV["GERRIT_REFSPEC"]
    if link_to_guidelines
      message += "\n\nHere are some guidelines to keep our git log pretty and useful: http://chris.beams.io/posts/git-commit/"
    end

    # NOTE: add 6 to all line numbers, cuz parent/author/commit/dates/etc
    payload = { path: "/COMMIT_MSG", position: (line || 1) + 6, severity:, message: }
    `gergich comment #{Shellwords.escape(payload.to_json)}`
    warn "line #{line}: [#{severity}]: #{message}"
  elsif line
    warn $lines[line - 1]
    warn "  ^: [#{severity}]: #{message}"
  else
    warn "[#{severity}]: #{message}"
  end
end

commit_message = case ARGV[0]
                 when "--stdin" then $stdin.read
                 when nil then git("show --pretty=format:%B -s")
                 else File.read(ARGV[0])
                 end

exit if commit_message.match?(/\A\[?wip($|[\] :-])/i)
lines = commit_message.split("\n")
$lines = lines.dup
subject = lines.shift
# rubocop:enable Style/GlobalVars

if lines.shift != ""
  comment "error", 2, "add a blank line after the subject line", link_to_guidelines: true
end

SUBJECT_MAX_LINE_LEN = 75
SUBJECT_IDEAL_LINE_LEN = 65

length_exemption_regex = /^Revert/

if subject.size > SUBJECT_MAX_LINE_LEN && subject !~ length_exemption_regex
  comment "error",
          1,
          "subject line can't exceed #{SUBJECT_MAX_LINE_LEN} chars (ideally keep it well under #{SUBJECT_IDEAL_LINE_LEN})",
          link_to_guidelines: true
elsif subject.size > SUBJECT_IDEAL_LINE_LEN && subject !~ length_exemption_regex
  comment "warn", 1, "subject line shouldn't exceed #{SUBJECT_IDEAL_LINE_LEN} chars", link_to_guidelines: true
end

# not perfect (won't get irregular verbs, only checks first word), but
# close enough... does the right thing on most existing commits
maybe_wrong_tense_regex = /\A(spec: )?[a-z]+([^e]ed|[^aijoqsu]s|ing) /i
actually_ok_regex = /\A
  (spec:\s+)?
  (
    # *ing words
    (brand|br|[^ ]*learn|masquerad|upcom|shard|word|grad)ing |

    # *ed words
    (brand|fail|batch|emb|persist|enrich|(un)?publish|moderat|mut|grad)ed |

    # *s words
    (.*(ion|ment)|observer|alway|outcome|rail|user|spec|student|quizze|alert|plugin|term)s
  )
/xi

maybe_start_with_wrong_tense = subject =~ maybe_wrong_tense_regex &&
                               subject !~ actually_ok_regex
if maybe_start_with_wrong_tense
  comment "warn",
          1,
          "make sure your commit message has an imperative verb (e.g. \"add\" instead of \"adds\", \"added\", or \"adding\")",
          link_to_guidelines: true
end

has_the_word_i = subject =~ /(\A| )i('m)? /i
if has_the_word_i
  comment "warn", 1, "just say what the commit does, and not in the first person :P", link_to_guidelines: true
end

BODY_IDEAL_LINE_LENGTH = 75

long_lines = lines.each_with_index.select do |line, _i|
  line.size > BODY_IDEAL_LINE_LENGTH
end

unless long_lines.empty?
  comment "warn",
          long_lines.first[1] + 3,
          "try to keep all lines under #{BODY_IDEAL_LINE_LENGTH} characters" +
          ((long_lines.size > 1) ? " (note that #{long_lines.size - 1} other long lines follow this one)" : ""),
          link_to_guidelines: true
end

starts_with_spec = subject =~ /\Aspec: /i
exit if starts_with_spec # we're trusting you here, don't be evil

commit_files = git("show --pretty=format:'' --name-only").split("\n")
if ENV["GERRIT_REFSPEC"]
  puts "detected file changes"
  puts commit_files
end

touches_db_migrate_file = commit_files.any? { |f| f.start_with?("db/migrate/") }
if touches_db_migrate_file
  comment "info", nil, "Your commit modifies migration files. Please review and follow the instructions in https://instructure.atlassian.net/wiki/spaces/CE/pages/49643724/Rails+Migrations, including completing an additional migration review."
end

touches_permissions_registry_file = commit_files.any? { |f| f.start_with?("config/initializers/permissions_registry") }
if touches_permissions_registry_file
  comment "info", nil, "Your commit modifies the permissions registry file. Please review and follow the instructions in https://instructure.atlassian.net/wiki/spaces/CE/pages/85519794197/Users+Roles+and+Permissions, including adding an additional migration to backfill those permission(s)."
end

touches_brandable_variables_file = commit_files.any? { |f| f.start_with?("app/stylesheets/brandable_variables") }
if touches_brandable_variables_file
  comment "warn", nil, "Changes were made to the brandable variables file. You you will need to rename the corresponding database migration file that runs brand_configs. See the following migration file for more info: regenerate_brand_files_based_on_new_defaults_predeploy.rb."
end

touches_lti_variable_expander_file = commit_files.any? { |f| f.start_with?("lib/lti/variable_expander") }
touches_lti_variable_expander_docs = commit_files.any? { |f| f.start_with?("doc/api/tools_variable_substitutions") }
if touches_lti_variable_expander_file && !touches_lti_variable_expander_docs
  comment "warn", nil, "Changes were made to the variable expander file. The docs are created from the comments and there are no document changes. Do you need to update the docs also? If so, run `rake doc:api` and commit the changes. Otherwise (if you didn't modify the descriptions of any variable expansions, or if you added an internal-only expansion using @internal) you may ignore this warning."
end

gh_issue = (commit_message =~ /(?:refs|fixes|closes|resolves|references) gh-\d+/i)
SAFE_PARTS_REGEX = /UTF-8/
TICKET_REGEX = /([A-Z]+-[0-9]+)/

issue_ids = JiraRefParser.scan_message_for_issue_ids(commit_message)
if issue_ids.empty? && !gh_issue
  parts = commit_message.gsub(SAFE_PARTS_REGEX, "")
                        .split(TICKET_REGEX)
  if parts.size > 1
    jira_hook_words = JiraRefParser::RefKeywords + JiraRefParser::FixKeywords
    comment "warn",
            parts.first.count("\n") + 1,
            "the jira hooks won't link this commit to #{parts[1]} unless you use one of the following keywords: #{jira_hook_words.join(", ")}"
  else
    comment "warn", nil, "you should really reference a ticket ლ(ٱ٥ٱლ)"
  end
end

only_affects_specs = commit_files.all? { |f| f.start_with?("spec/") }
if only_affects_specs
  comment "warn", nil, "since your commit only affects specs, please prefix your commit message with \"spec: \""
  exit
end

flag_name = (commit_message =~ /^flag\s{0,3}=\s{0,3}([a-zA-Z][\w-]+)\s{0,3}$/)
unless flag_name
  comment "warn", nil, "ლ(ಠ益ಠლ) y u no add release flag? https://instructure.atlassian.net/wiki/spaces/CE/pages/603586589/Commit+Message+Release+Flags"
end

has_a_test_plan = commit_message =~ /test[ -]plan/i
unless has_a_test_plan
  comment "warn", nil, "y u no add test plan? ლ(ಠ益ಠლ)"
end

skip_eslint_flag = commit_message.include? "[skip-eslint]"
if skip_eslint_flag
  comment "error",
          nil,
          "[skip-eslint] should only be used for large restructuring changes where no real code changes are actually made"
end
