# NOTE: we are using `GithubIssue` to store issues, pull requests and comments
class GithubIssue < ApplicationRecord
  CATEGORIES = %w[issue issue_comment].freeze
  API_URL_REGEXP = %r{\Ahttps://api.github.com/repos/.*\z}.freeze
  PATH_COMMENT_REGEXP = %r{/issues/comments/}.freeze
  PATH_ISSUE_REGEXP = %r{/issues/}.freeze
  PATH_PULL_REQUEST_REGEXP = %r{/pulls/}.freeze
  PATH_REPO_REGEXP = %r{.*github.com/repos/}.freeze

  serialize :issue_serialized, Hash

  validates :category, inclusion: { in: CATEGORIES }
  validates :url, presence: true, length: { maximum: 400 }, format: API_URL_REGEXP

  class << self
    def find_or_fetch(url)
      find_by(url: url) || fetch(url)
    end

    private

    def fetch(url)
      retrieve_and_save_issue(url)
    rescue Github::Errors::NotFound => e
      raise e, error_message(url)
    end

    def retrieve_and_save_issue(url)
      issue = new(url: url)

      if PATH_COMMENT_REGEXP.match?(url)
        repo, issue_id = comment_repo_and_issue_id(url)
        issue.issue_serialized = Github::Client.issue_comment(repo, issue_id).to_h
        issue.category = "issue_comment"
      else
        repo, issue_id = issue_or_pull_repo_and_issue_id(url)
        issue.issue_serialized = Github::Client.issue(repo, issue_id).to_h
        issue.category = "issue"
      end

      # despite the counter intuitive name `.markdown` returns HTML rendered
      # from the original markdown
      issue.processed_html = Github::Client.markdown(issue.issue_serialized[:body])

      issue.save!

      issue
    end

    def clean_url(url)
      url.gsub(PATH_REPO_REGEXP, "")
    end

    def comment_repo_and_issue_id(url)
      clean_url(url).split(PATH_COMMENT_REGEXP)
    end

    def issue_or_pull_repo_and_issue_id(url)
      splitter = if PATH_PULL_REQUEST_REGEXP.match?(url)
                   PATH_PULL_REQUEST_REGEXP
                 else
                   PATH_ISSUE_REGEXP
                 end

      clean_url(url).split(splitter)
    end

    def error_message(url)
      if PATH_COMMENT_REGEXP.match?(url)
        _, issue_id = comment_repo_and_issue_id(url)
        "Issue comment #{issue_id} not found"
      else
        _, issue_id = issue_or_pull_repo_and_issue_id(url)
        "Issue #{issue_id} not found"
      end
    end
  end
end
