# frozen_string_literal: true

module API
  module Helpers
    module Kubernetes
      module AgentHelpers
        include Gitlab::Utils::StrongMemoize

        COUNTERS_EVENTS_MAPPING = {
          'flux_git_push_notifications_total' => 'create_flux_git_push_notification',
          'k8s_api_proxy_request' => 'request_api_proxy_access',
          'k8s_api_proxy_requests_via_ci_access' => 'request_api_proxy_access_via_ci',
          'k8s_api_proxy_requests_via_user_access' => 'request_api_proxy_access_via_user',
          'k8s_api_proxy_requests_via_pat_access' => 'request_api_proxy_access_via_pat'
        }.freeze

        def agent_token
          cluster_agent_token_from_authorization_token
        end
        strong_memoize_attr :agent_token

        def agent
          agent_token.agent
        end
        strong_memoize_attr :agent

        def check_agent_token
          unauthorized! unless agent_token

          ::Clusters::AgentTokens::TrackUsageService.new(agent_token).execute
        end

        def agent_has_access_to_project?(project)
          ::Users::Anonymous.can?(:download_code, project) || agent.has_access_to?(project)
        end

        def increment_unique_events
          events = params[:unique_counters]&.slice(
            :k8s_api_proxy_requests_unique_agents_via_ci_access,
            :k8s_api_proxy_requests_unique_agents_via_user_access,
            :k8s_api_proxy_requests_unique_agents_via_pat_access,
            :flux_git_push_notified_unique_projects
          )

          events&.each do |event, entity_ids|
            increment_unique_values(event, entity_ids)
          end
        end

        def track_events
          event_lists = params[:events]&.slice(
            :k8s_api_proxy_requests_unique_users_via_ci_access,
            :k8s_api_proxy_requests_unique_users_via_user_access,
            :k8s_api_proxy_requests_unique_users_via_pat_access,
            :register_agent_at_kas
          )
          return if event_lists.blank?

          event_lists[:agent_users_using_ci_tunnel] = event_lists.slice(
            :k8s_api_proxy_requests_unique_users_via_ci_access,
            :k8s_api_proxy_requests_unique_users_via_user_access,
            :k8s_api_proxy_requests_unique_users_via_pat_access
          ).values.compact.flatten

          users, projects = load_users_and_projects(event_lists)
          event_lists.each do |event_name, events|
            track_events_for(event_name, events, users, projects) if events
          end
        end

        def track_unique_user_events
          events = params[:unique_counters]&.slice(
            :k8s_api_proxy_requests_unique_users_via_ci_access,
            :k8s_api_proxy_requests_unique_users_via_user_access,
            :k8s_api_proxy_requests_unique_users_via_pat_access
          )
          return if events.blank?

          unique_user_ids = events.values.flatten.uniq
          users = User.id_in(unique_user_ids).index_by(&:id)

          events.each do |event, user_ids|
            user_ids.each do |user_id|
              user = users[user_id]
              next if user.nil?

              Gitlab::InternalEvents.track_event(event, user: user)
            end
          end
        end

        def increment_count_events
          counters = params[:counters]&.slice(*COUNTERS_EVENTS_MAPPING.keys)

          return unless counters.present?

          counters.each do |counter, incr|
            next if incr == 0

            event = COUNTERS_EVENTS_MAPPING[counter]

            Gitlab::InternalEvents.with_batched_redis_writes do
              incr.times { Gitlab::InternalEvents.track_event(event) }
            end
          end
        end

        def update_configuration(agent:, config:)
          ::Clusters::Agents::Authorizations::CiAccess::RefreshService.new(agent, config: config).execute
          ::Clusters::Agents::Authorizations::UserAccess::RefreshService.new(agent, config: config).execute
        end

        def retrieve_user_from_session_cookie
          # Load session
          public_session_id_string =
            begin
              Gitlab::Kas::UserAccess.decrypt_public_session_id(params[:access_key])
            rescue StandardError
              bad_request!('Invalid access_key')
            end

          session_id = Rack::Session::SessionId.new(public_session_id_string)
          session = ActiveSession.sessions_from_ids([session_id.private_id]).first
          unauthorized!('Invalid session') unless session

          # CSRF check
          unless ::Gitlab::Kas::UserAccess.valid_authenticity_token?(session.symbolize_keys, params[:csrf_token])
            unauthorized!('CSRF token does not match')
          end

          # Load user
          user = Warden::SessionSerializer.new('rack.session' => session).fetch(:user)
          unauthorized!('Invalid user in session') unless user
          user
        end

        def retrieve_user_from_personal_access_token
          return unless access_token.present?

          validate_and_save_access_token!(scopes: [Gitlab::Auth::K8S_PROXY_SCOPE])

          ::PersonalAccessTokens::LastUsedService.new(access_token).execute

          access_token.user || raise(UnauthorizedError)
        end

        def set_agent_on_context(agent:)
          ::Gitlab::ApplicationContext.push(kubernetes_agent: agent)
        end

        def access_token
          return unless params[:access_key].present?

          PersonalAccessToken.find_by_token(params[:access_key])
        end
        strong_memoize_attr :access_token

        private

        def load_users_and_projects(event_lists)
          all_events = event_lists.values.flatten.compact
          unique_user_ids = all_events.pluck('user_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
          unique_project_ids = all_events.pluck('project_id').compact.uniq # rubocop:disable CodeReuse/ActiveRecord -- this pluck isn't from ActiveRecord, it's from ActiveSupport
          users = User.id_in(unique_user_ids).index_by(&:id)
          projects = Project.id_in(unique_project_ids).index_by(&:id)
          [users, projects]
        end

        def track_events_for(event_name, events, users, projects)
          events.each do |event|
            next if event.blank?

            user = users[event[:user_id]]
            project = projects[event[:project_id]]
            next if project.nil?

            additional_properties = {}
            if event_name.to_sym == :register_agent_at_kas
              additional_properties = {
                # built-in properties
                label: event[:agent_version],
                property: event[:architecture]
              }
              additional_properties[:value] = event[:agent_id] if event[:agent_id].present?

              # custom properties
              installation_method = event.dig(:extra_telemetry_data, :installation_method)
              helm_chart_version = event.dig(:extra_telemetry_data, :helm_chart_version)

              if event[:kubernetes_version].present?
                additional_properties[:kubernetes_version] = event[:kubernetes_version]
              end

              additional_properties[:installation_method] = installation_method if installation_method.present?
              additional_properties[:helm_chart_version] = helm_chart_version if helm_chart_version.present?
            end

            Gitlab::InternalEvents.track_event(event_name, additional_properties: additional_properties, user: user,
              project: project)
          end
        end
      end
    end
  end
end
