module OauthAuthentication
  extend ActiveSupport::Concern
  include ErrorReporting

  class_methods do
    include ErrorReporting

    def hca_authorize_url(redirect_uri)
      params = {
        redirect_uri:,
        client_id: ENV["HCA_CLIENT_ID"],
        response_type: "code",
        scope: "email slack_id verification_status theseus:send_mail"
      }

      URI.parse("#{HCAService.host}/oauth/authorize?#{params.to_query}")
    end

    def slack_authorize_url(redirect_uri, state: nil, close_window: false, continue_param: nil)
      state ||= {
        token: SecureRandom.hex(24),
        close_window: close_window,
        continue: continue_param
      }.to_json

      params = {
        client_id: ENV["SLACK_CLIENT_ID"],
        redirect_uri: redirect_uri,
        state: state,
        user_scope: "users.profile:read,users.profile:write,users:read,users:read.email"
      }

      URI.parse("https://slack.com/oauth/v2/authorize?#{params.to_query}")
    end

    def github_authorize_url(redirect_uri, state: nil)
      params = {
        client_id: ENV["GITHUB_CLIENT_ID"],
        redirect_uri: redirect_uri,
        state: state || SecureRandom.hex(24),
        scope: "user:email"
      }

      URI.parse("https://github.com/login/oauth/authorize?#{params.to_query}")
    end

    def from_hca_token(code, redirect_uri)
      response = HTTP.post("#{HCAService.host}/oauth/token", form: {
        client_id: ENV["HCA_CLIENT_ID"],
        client_secret: ENV["HCA_CLIENT_SECRET"],
        redirect_uri: redirect_uri,
        code: code,
        grant_type: "authorization_code"
      })

      data = JSON.parse(response.body.to_s)

      access_token = data["access_token"]
      return nil if access_token.nil?

      hca_data = ::HCAService.me(access_token)
      identity = hca_data["identity"]
      @user = User.find_by_hca_id(identity["id"]) unless identity["id"].blank?
      @user ||= User.find_by_slack_uid(identity["slack_id"]) unless identity["slack_id"].blank?
      @user ||= begin
                  EmailAddress.find_by(email: identity["primary_email"])&.user unless identity["primary_email"].blank?
                end

      @user.update(
        hca_scopes: hca_data["scopes"],
        hca_id: identity["id"],
        hca_access_token: access_token
      ) if !!@user

      @user ||= begin
                  u = User.create!(
                    hca_id: identity["id"],
                    slack_uid: identity["slack_id"],
                    hca_scopes: hca_data["scopes"],
                    hca_access_token: access_token,
                  )
                  EmailAddress.create!(email: identity["primary_email"], user: u) unless identity["primary_email"].blank?
                  u
                end
    end

    def from_slack_token(code, redirect_uri)
      response = HTTP.post("https://slack.com/api/oauth.v2.access", form: {
        client_id: ENV["SLACK_CLIENT_ID"],
        client_secret: ENV["SLACK_CLIENT_SECRET"],
        code: code,
        redirect_uri: redirect_uri
      })

      data = JSON.parse(response.body.to_s)

      return nil unless data["ok"]

      user_response = HTTP.auth("Bearer #{data['authed_user']['access_token']}")
        .get("https://slack.com/api/users.info?user=#{data['authed_user']['id']}")

      user_data = JSON.parse(user_response.body.to_s)

      return nil unless user_data["ok"]

      slack_user = user_data["user"] || {}
      profile = slack_user["profile"] || {}

      email = profile["email"]&.downcase
      email_address = EmailAddress.find_or_initialize_by(email: email)
      user = email_address.user
      user ||= begin
        u = User.find_or_initialize_by(slack_uid: data.dig("authed_user", "id"))
        unless u.email_addresses.include?(email_address)
          u.email_addresses << email_address
        end
        u
      end

      user.email_addresses.source_slack.where.not(email: email).update_all(source: :signing_in)
      email_address.source = :slack
      email_address.save! if email_address.persisted?

      user.slack_uid = data.dig("authed_user", "id")
      user.slack_username = profile["display_name_normalized"].presence
      user.slack_username ||= profile["real_name_normalized"].presence
      user.slack_username ||= slack_user["name"].presence
      user.slack_avatar_url = profile["image_192"] || profile["image_72"]

      user.parse_and_set_timezone(slack_user["tz"])

      user.slack_access_token = data["authed_user"]["access_token"]
      user.slack_scopes = data["authed_user"]["scope"]&.split(/,\s*/)

      user.save!
      user
    rescue => e
      report_error(e, message: "Error creating user from Slack data: #{e.message}")
      nil
    end

    def from_github_token(code, redirect_uri, current_user)
      return nil unless current_user

      response = HTTP.headers(accept: "application/json")
        .post("https://github.com/login/oauth/access_token", form: {
          client_id: ENV["GITHUB_CLIENT_ID"],
          client_secret: ENV["GITHUB_CLIENT_SECRET"],
          code: code,
          redirect_uri: redirect_uri
        })

      data = JSON.parse(response.body.to_s)
      return nil unless data["access_token"]

      user_response = HTTP.auth("Bearer #{data['access_token']}")
        .get("https://api.github.com/user")

      user_data = JSON.parse(user_response.body.to_s)

      github_uid = user_data["id"]
      other_users = User.where(github_uid: github_uid).where.not(id: current_user.id).where.not(github_access_token: nil)

      other_users.find_each do |user|
        Rails.logger.info "Clearing GitHub token for User ##{user.id} (GitHub UID: #{github_uid}) - linking to new account"
        user.update!(github_access_token: nil, github_uid: nil, github_username: nil)
      end

      current_user.github_uid = github_uid
      current_user.github_username = user_data["login"].presence || user_data["name"].presence
      current_user.github_avatar_url = user_data["avatar_url"]
      current_user.github_access_token = data["access_token"]

      current_user.save!

      ScanGithubReposJob.perform_later(current_user.id)

      current_user
    rescue => e
      report_error(e, message: "Error linking GitHub account: #{e.message}")
      nil
    end
  end
end
