# frozen_string_literal: true

require "danger/helpers/comments_helper"

module Danger
  module RequestSources
    class BitbucketCloudAPI
      attr_accessor :host, :project, :slug, :access_token, :pull_request_id
      attr_reader :my_uuid

      def initialize(repo_slug, pull_request_id, branch_name, environment)
        initialize_my_uuid(environment["DANGER_BITBUCKETCLOUD_UUID"])
        @username = environment["DANGER_BITBUCKETCLOUD_USERNAME"]
        @password = environment["DANGER_BITBUCKETCLOUD_PASSWORD"]
        self.project, self.slug = repo_slug.split("/")
        self.access_token = fetch_access_token(environment)
        self.pull_request_id = pull_request_id || fetch_pr_from_branch(branch_name)
        self.host = "https://bitbucket.org/"
      end

      def initialize_my_uuid(uuid)
        return if uuid.nil?
        return @my_uuid = uuid if uuid.empty?

        if uuid.start_with?("{") && uuid.end_with?("}")
          @my_uuid = uuid
        else
          @my_uuid = "{#{uuid}}"
        end
      end

      def inspect
        inspected = super

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

        inspected
      end

      def credentials_given?
        return true if @access_token

        @my_uuid && !@my_uuid.empty? &&
          @username && !@username.empty? &&
          @password && !@password.empty?
      end

      def pull_request(*)
        fetch_pr_json
      end

      def fetch_pr_json
        uri = URI(pr_api_endpoint)
        fetch_json(uri)
      end

      def fetch_comments
        values = []
        # TODO: use a url parts encoder to encode the query
        corrected_uuid = @my_uuid[1...-1] unless @my_uuid.nil? # Endpoint doesnt support curly brackets for this, so remove them for this
        uri = "#{pr_api_endpoint}/comments?pagelen=100&q=deleted+%7E+false+AND+user.uuid+%7E+%22#{corrected_uuid}%22"

        while uri
          json = fetch_json(URI(uri))
          values += json[:values]
          uri = json[:next]
        end
        values
      end

      def delete_comment(id)
        uri = URI("#{pr_api_endpoint}/comments/#{id}")
        delete(uri)
      end

      def post_comment(text, file: nil, line: nil)
        uri = URI("#{pr_api_endpoint}/comments")
        body = {
          content: {
            raw: text
          }
        }
        body.merge!(inline: { path: file, to: line }) if file && line

        post(uri, body.to_json)
      end

      private

      def base_url(version)
        "https://api.bitbucket.org/#{version}.0/repositories/#{project}/#{slug}/pullrequests"
      end

      def pr_api_endpoint
        "#{base_url(2)}/#{pull_request_id}"
      end

      def prs_api_url(branch_name)
        encoded_branch_name = URI.encode_www_form_component(branch_name)
        "#{base_url(2)}?q=source.branch.name=\"#{encoded_branch_name}\""
      end

      def fetch_pr_from_branch(branch_name)
        uri = URI(prs_api_url(branch_name))
        fetch_json(uri)[:values][0][:id]
      end

      def fetch_access_token(environment)
        access_token = environment["DANGER_BITBUCKETCLOUD_REPO_ACCESSTOKEN"]
        if access_token
          @access_token = access_token
          return access_token
        end

        oauth_key = environment["DANGER_BITBUCKETCLOUD_OAUTH_KEY"]
        oauth_secret = environment["DANGER_BITBUCKETCLOUD_OAUTH_SECRET"]
        return nil if oauth_key.nil?
        return nil if oauth_secret.nil?

        uri = URI.parse("https://bitbucket.org/site/oauth2/access_token")
        req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
        req.basic_auth oauth_key, oauth_secret
        req.set_form_data({ "grant_type" => "client_credentials" })
        res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
          http.request(req)
        end

        JSON.parse(res.body, symbolize_names: true)[:access_token]
      end

      def fetch_json(uri)
        raise credentials_not_available unless credentials_given?

        req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json" })
        if access_token.nil?
          req.basic_auth @username, @password
        else
          req["Authorization"] = "Bearer #{access_token}"
        end
        res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
          http.request(req)
        end
        raise error_fetching_json(uri.to_s, res.code) unless res.code == "200"

        JSON.parse(res.body, symbolize_names: true)
      end

      def post(uri, body)
        raise credentials_not_available unless credentials_given?

        req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json" })
        if access_token.nil?
          req.basic_auth @username, @password
        else
          req["Authorization"] = "Bearer #{access_token}"
        end
        req.body = body
        Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
          http.request(req)
        end
      end

      def delete(uri)
        raise credentials_not_available unless credentials_given?

        req = Net::HTTP::Delete.new(uri.request_uri, { "Content-Type" => "application/json" })
        if access_token.nil?
          req.basic_auth @username, @password
        else
          req["Authorization"] = "Bearer #{access_token}"
        end
        Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
          http.request(req)
        end
      end

      def credentials_not_available
        "Credentials not available. Provide DANGER_BITBUCKETCLOUD_USERNAME, " \
        "DANGER_BITBUCKETCLOUD_UUID, and DANGER_BITBUCKETCLOUD_PASSWORD " \
        "as environment variables."
      end

      def error_fetching_json(url, status_code)
        "Error fetching json for: #{url}, status code: #{status_code}"
      end
    end
  end
end
