# frozen_string_literal: true

require "spec_helper"

RSpec.describe Admin::ApplicationSettingsHelper, feature_category: :ai_abstraction_layer do
  using RSpec::Parameterized::TableSyntax

  let(:duo_availability) { :default_off }
  let(:instance_level_ai_beta_features_enabled) { false }
  let(:model_prompt_cache_enabled) { true }
  let(:disabled_direct_code_suggestions) { false }
  let(:enabled_expanded_logging) { true }
  let(:duo_chat_expiration_column) { 'created_at' }
  let(:duo_chat_expiration_days) { 25 }
  let(:code_suggestions_service) { instance_double(CloudConnector::AvailableServices) }

  before do
    stub_ee_application_setting(duo_availability: duo_availability)
    stub_ee_application_setting(instance_level_ai_beta_features_enabled: instance_level_ai_beta_features_enabled)
    stub_ee_application_setting(model_prompt_cache_enabled: model_prompt_cache_enabled)
    stub_ee_application_setting(enabled_expanded_logging: enabled_expanded_logging)
    stub_ee_application_setting(disabled_direct_code_suggestions: disabled_direct_code_suggestions)
    stub_ee_application_setting(duo_chat_expiration_column: duo_chat_expiration_column)
    stub_ee_application_setting(duo_chat_expiration_days: duo_chat_expiration_days)
    allow(CloudConnector::AvailableServices)
      .to receive(:find_by_name).with(:code_suggestions).and_return(code_suggestions_service)
  end

  describe 'AI-native features settings for Self-Managed instances' do
    describe '#admin_ai_configuration_settings_helper_data' do
      subject(:admin_ai_configuration_settings_helper_data) { helper.admin_ai_configuration_settings_helper_data }

      before do
        allow(helper).to receive(:ai_settings_helper_data).and_return({ base_data: 'data' })
      end

      it 'returns the expected data' do
        expect(admin_ai_configuration_settings_helper_data).to include(
          on_general_settings_page: 'false',
          redirect_path: '/admin/gitlab_duo',
          base_data: 'data'
        )
      end
    end

    describe '#ai_settings_helper_data' do
      using RSpec::Parameterized::TableSyntax

      subject { helper.ai_settings_helper_data }

      let(:service) { double('CodeSuggestionsService') } # rubocop:disable RSpec/VerifiedDoubles -- Stubbed to test purchases call
      let(:enterprise_service) { double('EnterpriseService') } # rubocop:disable RSpec/VerifiedDoubles -- Stubbed to test purchases call

      let(:ai_gateway_url) { "http://0.0.0.0:5052" }
      let(:duo_availability) { 'default_on' }
      let(:instance_level_ai_beta_features_enabled) { false }
      let(:model_prompt_cache_enabled) { 'true' }
      let(:enabled_expanded_logging) { false }
      let(:disabled_direct_code_suggestions) { false }
      let(:duo_chat_expiration_column) { 'created_at' }
      let(:duo_chat_expiration_days) { 30 }
      let(:duo_core_features_enabled) { false }

      where(
        :terms_accepted,
        :purchased,
        :ultimate,
        :premium,
        :duo_ent_purchased,
        :expected_duo_pro_visible_value,
        :expected_experiments_visible_value,
        :expected_can_manage_self_hosted_models,
        :expected_duo_core_features_enabled
      ) do
        true  | true  | true  | false | true  | 'true'  | 'true'  | 'true'  | 'true'
        true  | true  | false | true  | true  | 'true'  | 'true'  | 'true'  | 'true'
        true  | true  | true  | false | false | 'true'  | 'true'  | 'false' | 'true'
        true  | true  | false | true  | false | 'true'  | 'true'  | 'false' | 'false'
        true  | true  | false | false | true  | 'true'  | 'true'  | 'false' | 'false'
        false | false | false | false | false | 'false' | 'false' | 'false' | 'false'
        true  | nil   | true  | false | false | ''      | 'false' | 'false' | 'false'
      end

      with_them do
        let(:expected_settings_helper_data) do
          {
            duo_availability: duo_availability.to_s,
            experiment_features_enabled: instance_level_ai_beta_features_enabled.to_s,
            prompt_cache_enabled: model_prompt_cache_enabled,
            are_experiment_settings_allowed: expected_experiments_visible_value.to_s,
            are_prompt_cache_settings_allowed: 'true',
            enabled_expanded_logging: enabled_expanded_logging.to_s,
            disabled_direct_connection_method: disabled_direct_code_suggestions.to_s,
            beta_self_hosted_models_enabled: terms_accepted.to_s,
            toggle_beta_models_path: admin_ai_duo_self_hosted_toggle_beta_models_path,
            duo_pro_visible: expected_duo_pro_visible_value,
            can_manage_self_hosted_models: expected_can_manage_self_hosted_models.to_s,
            ai_gateway_url: ai_gateway_url,
            duo_chat_expiration_column: duo_chat_expiration_column,
            duo_chat_expiration_days: duo_chat_expiration_days.to_s,
            duo_core_features_enabled: expected_duo_core_features_enabled.to_s,
            is_duo_base_access_allowed: 'true',
            duo_pro_or_duo_enterprise_tier: nil,
            should_show_duo_availability: 'false'
          }
        end

        before do
          allow(::Gitlab::CurrentSettings)
            .to receive(:disabled_direct_code_suggestions)
            .and_return(disabled_direct_code_suggestions)

          allow(helper).to receive_messages(
            experiments_settings_allowed?: expected_experiments_visible_value == 'true',
            prompt_cache_settings_allowed?: true,
            duo_availability: duo_availability,
            instance_level_ai_beta_features_enabled: instance_level_ai_beta_features_enabled,
            enabled_expanded_logging: enabled_expanded_logging,
            current_application_settings: double( # rubocop:disable RSpec/VerifiedDoubles -- Stubbed to test expiration call
              duo_chat_expiration_column: duo_chat_expiration_column,
              duo_chat_expiration_days: duo_chat_expiration_days,
              model_prompt_cache_enabled: model_prompt_cache_enabled
            )
          )

          allow(::Ai::TestingTermsAcceptance).to receive(:has_accepted?).and_return(terms_accepted)

          allow(License).to receive_message_chain(:current, :ultimate?).and_return(ultimate)
          allow(License).to receive_message_chain(:current, :premium?).and_return(premium)

          allow(::GitlabSubscriptions::AddOnPurchase)
            .to receive_message_chain(:for_self_managed, :for_duo_enterprise, :active, :exists?)
            .and_return(duo_ent_purchased)

          allow(::GitlabSubscriptions::AddOnPurchase)
            .to receive_message_chain(:for_self_managed, :for_duo_pro_or_duo_enterprise, :active, :first)

          allow(::Ai::Setting).to receive_message_chain(:instance, :ai_gateway_url).and_return(ai_gateway_url)
          allow(::Ai::Setting).to receive_message_chain(:instance, :enabled_instance_verbose_ai_logs)
            .and_return(enabled_expanded_logging)

          setup_cloud_connector_services(purchased)

          allow(::Ai::Setting).to receive_message_chain(:instance, :duo_core_features_enabled?)
            .and_return expected_duo_core_features_enabled
        end

        it 'returns the expected data' do
          is_expected.to eq(expected_settings_helper_data)
        end

        context 'with feature flag allow_duo_base_access set to false' do
          before do
            stub_feature_flags(allow_duo_base_access: false)
          end

          it 'sets is_duo_base_access_allowed to false' do
            expect(helper.ai_settings_helper_data).to include(is_duo_base_access_allowed: 'false')
          end
        end
      end

      def setup_cloud_connector_services(purchased)
        if purchased.nil?
          allow(CloudConnector::AvailableServices)
            .to receive(:find_by_name).with(:code_suggestions).and_return(nil)
        else
          allow(CloudConnector::AvailableServices)
            .to receive(:find_by_name).with(:code_suggestions).and_return(service)
          allow(service).to receive(:purchased?).and_return(purchased)
        end

        allow(CloudConnector::AvailableServices)
          .to receive(:find_by_name).with(:anthropic_proxy).and_return(enterprise_service)
        allow(enterprise_service).to receive(:purchased?).and_return(true)
      end
    end
  end

  describe '#ai_settings_helper_data[:duo_pro_or_duo_enterprise_tier]' do
    subject { helper.ai_settings_helper_data[:duo_pro_or_duo_enterprise_tier] }

    before do
      allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:code_suggestions)
      allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:anthropic_proxy)

      allow(GitlabSubscriptions::Trials::DuoProOrDuoEnterprise)
        .to receive(:any_add_on_purchase)
        .with(nil)
        .and_return(duo_pro_or_duo_enterprise_add_on_purchase)
    end

    context 'with Duo Pro' do
      let(:duo_pro_or_duo_enterprise_add_on_purchase) do
        build(:gitlab_subscription_add_on_purchase, :duo_pro, :self_managed, :active)
      end

      it { is_expected.to eq 'CODE_SUGGESTIONS' }
    end

    context 'with Duo Enterprise' do
      let(:duo_pro_or_duo_enterprise_add_on_purchase) do
        build(:gitlab_subscription_add_on_purchase, :duo_enterprise, :self_managed, :active)
      end

      it { is_expected.to eq 'DUO_ENTERPRISE' }
    end
  end

  describe '#ai_settings_helper_data[:should_show_duo_availability]' do
    subject { helper.ai_settings_helper_data[:should_show_duo_availability] }

    before do
      allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:code_suggestions)
      allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:anthropic_proxy)

      allow(GitlabSubscriptions::Trials::DuoProOrDuoEnterprise)
        .to receive(:any_add_on_purchased_or_trial?)
        .with(nil)
        .and_return(duo_pro_or_duo_enterprise_add_on_purchase.active?)
    end

    context 'with active Duo add-on' do
      let(:duo_pro_or_duo_enterprise_add_on_purchase) do
        build(:gitlab_subscription_add_on_purchase, :duo_pro, :active)
      end

      it { is_expected.to eq 'true' }
    end

    context 'with expired Duo add-on' do
      let(:duo_pro_or_duo_enterprise_add_on_purchase) do
        build(:gitlab_subscription_add_on_purchase, :duo_enterprise, :expired)
      end

      it { is_expected.to eq 'false' }
    end
  end

  describe '#admin_display_duo_addon_settings?' do
    subject(:display_duo_pro_settings) { helper.admin_display_duo_addon_settings? }

    let(:duo_add_on_purchased) { false }

    before do
      allow(GitlabSubscriptions::AddOnPurchase)
        .to receive_message_chain(:for_self_managed, :for_duo_core_pro_or_enterprise, :active, :any?)
        .and_return(duo_add_on_purchased)
    end

    context 'when a self-managed Duo Core, Duo Pro or Duo Enterprise purchase exists' do
      let(:duo_add_on_purchased) { true }

      it { is_expected.to be true }
    end

    context 'when no self-managed Duo Core, Duo Pro or Duo Enterprise purchase exists' do
      let(:duo_add_on_purchased) { false }

      it { is_expected.to be false }
    end
  end

  describe '#admin_duo_home_app_data' do
    let(:starts_at) { Date.current }
    let(:expires_at) { Date.current + 1.year }
    let(:license) { build(:gitlab_license, starts_at: starts_at, expires_at: expires_at) }
    let(:subscription_name) { 'Test Subscription Name' }
    let(:amazon_q_available) { false }
    let(:duo_workflow_enabled) { false }
    let(:duo_workflow_service_account) { nil }
    let(:is_saas) { false }
    let(:duo_core_features_enabled) { true }

    before do
      allow(License).to receive(:current).and_return(license)
      allow(license).to receive(:ultimate?).and_return(true)
      allow(::Ai::AmazonQ).to receive(:feature_available?).and_return(amazon_q_available)
      allow(license).to receive_messages(
        subscription_name: subscription_name,
        subscription_start_date: starts_at,
        subscription_end_date: expires_at
      )

      allow(helper).to receive_messages(
        admin_gitlab_duo_seat_utilization_index_path: '/admin/gitlab_duo/seat_utilization',
        admin_gitlab_duo_configuration_index_path: '/admin/gitlab_duo/configuration',
        admin_gitlab_duo_path: '/admin/gitlab_duo',
        admin_ai_duo_workflow_settings_path: '/admin/ai/duo_workflow/settings',
        disconnect_admin_ai_duo_workflow_settings_path: '/admin/ai/duo_workflow/settings/disconnect',
        duo_pro_bulk_user_assignment_available?: true,
        duo_availability: 'default_off',
        instance_level_ai_beta_features_enabled: true,
        experiments_settings_allowed?: true,
        duo_workflow_service_account: duo_workflow_service_account
      )

      allow(helper).to receive(:add_duo_pro_seats_url).with(subscription_name).and_return('https://customers.staging.gitlab.com/gitlab/subscriptions/A-S00613274/duo_pro_seats')
      allow(Gitlab::CurrentSettings).to receive_message_chain(:current,
        :disabled_direct_code_suggestions).and_return(false)
      allow(::Ai::TestingTermsAcceptance).to receive(:has_accepted?).and_return(true)
      allow(::Ai::DuoWorkflow).to receive(:available?).and_return(duo_workflow_enabled)
      allow(::Gitlab).to receive(:com?).and_return(is_saas)

      allow(::Ai::Setting).to receive_message_chain(:instance, :duo_core_features_enabled?)
        .and_return(duo_core_features_enabled)
      allow(::Ai::Setting).to receive_message_chain(:instance, :ai_gateway_url)
      .and_return('http://0.0.0.0:5052')
    end

    it 'returns a hash with all required keys and correct values' do
      expect(helper.admin_duo_home_app_data).to eq({
        ai_gateway_url: 'http://0.0.0.0:5052',
        duo_seat_utilization_path: '/admin/gitlab_duo/seat_utilization',
        duo_configuration_path: '/admin/gitlab_duo/configuration',
        enabled_expanded_logging: 'true',
        add_duo_pro_seats_url: 'https://customers.staging.gitlab.com/gitlab/subscriptions/A-S00613274/duo_pro_seats',
        subscription_name: 'Test Subscription Name',
        is_bulk_add_on_assignment_enabled: 'true',
        is_duo_base_access_allowed: 'true',
        subscription_start_date: starts_at,
        subscription_end_date: expires_at,
        duo_availability: 'default_off',
        direct_code_suggestions_enabled: 'true',
        experiment_features_enabled: 'true',
        prompt_cache_enabled: 'true',
        beta_self_hosted_models_enabled: 'true',
        are_experiment_settings_allowed: 'true',
        are_prompt_cache_settings_allowed: 'true',
        duo_workflow_enabled: 'false',
        duo_workflow_service_account: nil,
        is_saas: 'false',
        duo_workflow_settings_path: '/admin/ai/duo_workflow/settings',
        duo_workflow_disable_path: '/admin/ai/duo_workflow/settings/disconnect',
        duo_self_hosted_path: '/admin/ai/duo_self_hosted',
        redirect_path: '/admin/gitlab_duo',
        can_manage_self_hosted_models: 'false',
        duo_add_on_start_date: nil,
        duo_add_on_end_date: nil,
        are_duo_core_features_enabled: 'true'
      })
    end

    context 'with disabled duo_core_features_enabled' do
      let(:duo_core_features_enabled) { false }

      it 'sets Duo Core flag to false' do
        expect(helper.admin_duo_home_app_data).to include(are_duo_core_features_enabled: 'false')
      end
    end

    context 'when the instance is SaaS' do
      let(:is_saas) { true }

      it 'sets is_saas to true' do
        expect(helper.admin_duo_home_app_data[:is_saas]).to eq('true')
      end
    end

    context 'when the instance is Gitlab Dedicated' do
      before do
        allow(Gitlab::CurrentSettings).to receive(:gitlab_dedicated_instance?).and_return(true)
      end

      it 'sets can_manage_self_hosted_models to false' do
        expect(helper.admin_duo_home_app_data[:can_manage_self_hosted_models]).to eq('false')
      end
    end

    context 'with feature flag allow_duo_base_access set to false' do
      before do
        stub_feature_flags(allow_duo_base_access: false)
      end

      it 'sets is_duo_base_access_allowed to false' do
        expect(helper.admin_duo_home_app_data).to include(is_duo_base_access_allowed: 'false')
      end
    end

    context 'when the instance has a Duo purchase' do
      let(:duo_start_date) { Date.current - 1.month }
      let(:duo_end_date) { Date.current + 11.months }
      let(:duo_purchase) do
        build(
          :gitlab_subscription_add_on_purchase, :self_managed, :duo_enterprise,
          started_at: duo_start_date,
          expires_on: duo_end_date
        )
      end

      before do
        allow(GitlabSubscriptions::AddOnPurchase)
          .to receive_message_chain(:for_self_managed, :for_duo_pro_or_duo_enterprise, :last)
          .and_return(duo_purchase)

        allow(::GitlabSubscriptions::AddOnPurchase)
          .to receive_message_chain(:for_self_managed, :for_duo_enterprise, :active, :exists?)
          .and_return(true)
      end

      it 'includes the correct values' do
        result = helper.admin_duo_home_app_data

        expect(result).to include(duo_add_on_start_date: duo_start_date, duo_add_on_end_date: duo_end_date)
      end
    end

    context 'when direct connections is disabled' do
      before do
        allow(Gitlab::CurrentSettings).to receive(:disabled_direct_code_suggestions).and_return(true)
      end

      it 'returns the correct value' do
        expect(helper.admin_duo_home_app_data[:direct_code_suggestions_enabled]).to eq 'false'
      end
    end

    context 'when Amazon Q is available' do
      let(:amazon_q_available) { true }

      where(:auto_review_enabled, :amazon_q_ready) do
        false | true
        true  | false
      end

      with_them do
        let(:integration) { build_stubbed(:amazon_q_integration, auto_review_enabled: auto_review_enabled) }

        it 'includes the related data' do
          allow(::Integrations::AmazonQ).to receive(:for_instance).and_return([integration])
          allow(::Ai::Setting.instance).to receive(:amazon_q_ready).and_return(amazon_q_ready)

          expect(helper.admin_duo_home_app_data).to include(
            amazon_q_ready: amazon_q_ready.to_s,
            amazon_q_auto_review_enabled: auto_review_enabled.to_s,
            amazon_q_configuration_path: '/admin/application_settings/integrations/amazon_q/edit'
          )
        end
      end
    end

    context 'when duo workflow service account user exists' do
      let(:duo_workflow_enabled) { true }
      let(:service_account) { build_stubbed(:user, id: 123, username: 'duo_service', name: 'Duo Service') }
      let(:user_data) { { id: 123, username: 'duo_service', name: 'Duo Service', avatar_url: 'avatar.png' } }
      let(:ai_setting) do
        instance_double(Ai::Setting, duo_core_features_enabled?: true, ai_gateway_url: 'http://0.0.0.0:5052')
      end

      before do
        allow(helper).to receive(:duo_workflow_service_account).and_call_original
        allow(license).to receive(:feature_available?).and_return(false)

        allow(Ai::Setting).to receive(:instance).and_return(ai_setting)
        allow(ai_setting).to receive(:duo_workflow_service_account_user).and_return(service_account)

        allow(service_account).to receive(:slice).with(:id, :username, :name, :avatar_url).and_return(user_data)
      end

      it 'includes the sliced and JSON-converted service account data' do
        result = helper.admin_duo_home_app_data

        expect(result[:duo_workflow_enabled]).to eq('true')
        expect(result[:duo_workflow_service_account]).to eq(
          '{"id":123,"username":"duo_service","name":"Duo Service","avatar_url":"avatar.png"}'
        )
      end
    end
  end
end
