# frozen_string_literal: true

module Danger
  module RequestSources
    #
    # Provides ability for Danger to interact with Atlassian's Code Insights API in order to provide code quality
    # reports along with inline comments for specific lines in specific files.
    #  See https://developer.atlassian.com/server/bitbucket/how-tos/code-insights/ for more details.
    #
    # Currently this functionality is implemented only for Bitbucket Server request source.
    class CodeInsightsAPI
      attr_accessor :username, :password, :host, :report_key, :report_title, :report_description, :logo_url

      def initialize(project, slug, environment)
        @username = environment["DANGER_BITBUCKETSERVER_USERNAME"] || ""
        @password = environment["DANGER_BITBUCKETSERVER_PASSWORD"] || ""
        @host = environment["DANGER_BITBUCKETSERVER_HOST"] || ""
        @report_key = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_KEY"] || ""
        @report_title = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_TITLE"] || ""
        @report_description = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_DESCRIPTION"] || ""
        @logo_url = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_LOGO_URL"] || ""
        @project = project
        @slug = slug
      end

      def inspect
        inspected = super

        inspected.gsub!(@password, "********") if @password

        inspected
      end

      def ready?
        !(@report_key.empty? || @report_title.empty? || @report_description.empty? || @username.empty? || @password.empty? || @host.empty?)
      end

      def delete_report(commit)
        uri = URI(report_endpoint_at_commit(commit))
        request = Net::HTTP::Delete.new(uri.request_uri, { "Content-Type" => "application/json" })
        request.basic_auth @username, @password
        response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
          http.request(request)
        end

        # show failure when server returns an error
        case response
        when Net::HTTPClientError, Net::HTTPServerError
          # HTTP 4xx - 5xx
          abort "\nError deleting report from Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
        end
      end

      def send_report(commit, inline_warnings, inline_errors, inline_messages)
        delete_report(commit)
        put_report(commit, inline_errors.count)
        should_post_annotations = !(inline_warnings + inline_errors + inline_messages).empty?
        if should_post_annotations
          post_annotations(commit, inline_warnings, inline_errors, inline_messages)
        end
      end

      def put_report(commit, inline_errors_count)
        uri = URI(report_endpoint_at_commit(commit))
        request = Net::HTTP::Put.new(uri.request_uri, { "Content-Type" => "application/json" })
        request.basic_auth @username, @password
        request.body = { "title": @report_title,
                        "details": @report_description,
                        "result": inline_errors_count > 0 ? "FAIL" : "PASS",
                        "reporter": @username,
                        "link": "https://github.com/danger/danger",
                        "logoURL": @logo_url }.to_json

        response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
          http.request(request)
        end

        # show failure when server returns an error
        case response
        when Net::HTTPClientError, Net::HTTPServerError
          # HTTP 4xx - 5xx
          abort "\nError putting report to Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
        end
      end

      def post_annotations(commit, inline_warnings, inline_errors, inline_messages)
        uri = URI(annotation_endpoint_at_commit(commit))

        annotations = []

        inline_messages.each do |violation|
          annotations << violation_hash_with_severity(violation, "LOW")
        end

        inline_warnings.each do |violation|
          annotations << violation_hash_with_severity(violation, "MEDIUM")
        end

        inline_errors.each do |violation|
          annotations << violation_hash_with_severity(violation, "HIGH")
        end

        body = { annotations: annotations }.to_json
        request = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
        request.basic_auth @username, @password
        request.body = body

        response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
          http.request(request)
        end

        # show failure when server returns an error
        case response
        when Net::HTTPClientError, Net::HTTPServerError
          # HTTP 4xx - 5xx
          abort "\nError posting comment to Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
        end
      end

      def violation_hash_with_severity(violation, severity)
        annotation = {}
        annotation["message"] = violation.message
        annotation["severity"] = severity
        annotation["path"] = violation.file
        annotation["line"] = violation.line.to_i
        return annotation
      end

      def report_endpoint_at_commit(commit)
        "#{@host}/rest/insights/1.0/projects/#{@project}/repos/#{@slug}/commits/#{commit}/reports/#{@report_key}"
      end

      def annotation_endpoint_at_commit(commit)
        "#{report_endpoint_at_commit(commit)}/annotations"
      end

      def use_ssl
        @host.include? "https://"
      end
    end
  end
end
