require 'rails_helper'
require Rails.root.join 'spec/models/concerns/reauthorizable_shared.rb'

RSpec.describe Integrations::Hook do
  it_behaves_like 'reauthorizable'

  context 'with validations' do
    it { is_expected.to validate_presence_of(:app_id) }
    it { is_expected.to validate_presence_of(:account_id) }
  end

  describe 'associations' do
    it { is_expected.to belong_to(:account) }
  end

  describe 'when trying to create multiple hooks for an app' do
    let(:account) { create(:account) }

    context 'when app allows multiple hooks' do
      it 'allows to create succesfully' do
        create(:integrations_hook, account: account, app_id: 'webhook')
        expect(build(:integrations_hook, account: account, app_id: 'webhook').valid?).to be true
      end
    end

    context 'when app doesnot allow multiple hooks' do
      it 'throws invalid error' do
        create(:integrations_hook, account: account, app_id: 'slack')
        expect(build(:integrations_hook, account: account, app_id: 'slack').valid?).to be false
      end
    end
  end

  describe 'scopes' do
    let(:account) { create(:account) }
    let(:inbox) { create(:inbox, account: account) }
    let!(:account_hook) { create(:integrations_hook, account: account, app_id: 'webhook') }
    let!(:inbox_hook) do
      create(:integrations_hook,
             account: account,
             app_id: 'dialogflow',
             inbox: inbox,
             settings: {
               project_id: 'test-project',
               credentials: { type: 'service_account' }
             })
    end

    it 'returns account hooks' do
      expect(described_class.account_hooks.pluck(:id)).to include(account_hook.id)
      expect(described_class.account_hooks.pluck(:id)).not_to include(inbox_hook.id)
    end

    it 'returns inbox hooks' do
      expect(described_class.inbox_hooks.pluck(:id)).to include(inbox_hook.id)
      expect(described_class.inbox_hooks.pluck(:id)).not_to include(account_hook.id)
    end
  end

  describe '#crm_integration?' do
    let(:account) { create(:account) }

    before do
      account.enable_features('crm_integration')
    end

    it 'returns true for leadsquared integration' do
      hook = create(:integrations_hook,
                    account: account,
                    app_id: 'leadsquared',
                    settings: {
                      access_key: 'test',
                      secret_key: 'test',
                      endpoint_url: 'https://api.leadsquared.com'
                    })
      expect(hook.send(:crm_integration?)).to be true
    end

    it 'returns false for non-crm integrations' do
      hook = create(:integrations_hook, account: account, app_id: 'slack')
      expect(hook.send(:crm_integration?)).to be false
    end
  end

  describe '#trigger_setup_if_crm' do
    let(:account) { create(:account) }

    before do
      account.enable_features('crm_integration')
      allow(Crm::SetupJob).to receive(:perform_later)
    end

    context 'when integration is a CRM' do
      it 'enqueues setup job' do
        create(:integrations_hook,
               account: account,
               app_id: 'leadsquared',
               settings: {
                 access_key: 'test',
                 secret_key: 'test',
                 endpoint_url: 'https://api.leadsquared.com'
               })
        expect(Crm::SetupJob).to have_received(:perform_later)
      end
    end

    context 'when integration is not a CRM' do
      it 'does not enqueue setup job' do
        create(:integrations_hook, account: account, app_id: 'slack')
        expect(Crm::SetupJob).not_to have_received(:perform_later)
      end
    end
  end

  describe 'openai api key validation' do
    let(:account) { create(:account) }

    it 'prevents saving an openai hook with an invalid key' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(false)

      hook = build(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'sk-bad' })

      expect(hook).not_to be_valid
      expect(hook.errors[:base]).to include(I18n.t('errors.openai.invalid_api_key'))
    end

    it 'prevents saving an openai hook with a blank key' do
      hook = build(:integrations_hook, :openai, account: account, settings: { 'api_key' => '' })

      expect(hook).not_to be_valid
    end

    it 'allows saving an openai hook with a valid key' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(true)

      hook = build(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'sk-good' })

      expect(hook).to be_valid
    end

    it 'skips validation when an enabled openai hook is saved without changing the api key' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(true)
      hook = create(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'sk-good', 'label_suggestion' => false })

      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(false)
      hook.settings['label_suggestion'] = true

      expect(hook.save).to be true
    end

    it 'validates when a disabled openai hook is re-enabled' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(true)
      hook = create(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'sk-bad' })
      hook.update!(status: :disabled)

      allow(Integrations::Openai::KeyValidator).to receive(:valid?).with('sk-bad').and_return(false)

      expect(hook.update(status: :enabled)).to be false
      expect(hook.errors[:base]).to include(I18n.t('errors.openai.invalid_api_key'))
    end

    it 'skips validation for disabled hooks' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(true)
      hook = create(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'sk-good' })

      # Even with validator returning false, disable succeeds because disabled hooks skip validation
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(false)
      hook.disable
      expect(hook.reload).to be_disabled
    end

    it 'does not validate keys for non-openai hooks' do
      allow(Integrations::Openai::KeyValidator).to receive(:valid?).and_return(false)

      hook = build(:integrations_hook, account: account, app_id: 'slack')

      expect(hook).to be_valid
    end
  end

  describe 'cloudflare realtimekit credential validation' do
    let(:account) { create(:account) }
    let(:settings) { { 'account_id' => 'account_id', 'app_id' => 'app_id', 'api_token' => 'api_token' } }

    it 'prevents saving a RealtimeKit hook with an invalid API token' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :invalid_api_token))

      hook = build(:integrations_hook, :dyte, account: account, settings: settings)

      expect(hook).not_to be_valid
      expect(hook.errors[:base]).to include(I18n.t('errors.cloudflare.realtimekit.invalid_api_token'))
    end

    it 'prevents saving a RealtimeKit hook with an invalid account or missing token permissions' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :invalid_account_or_permissions))

      hook = build(:integrations_hook, :dyte, account: account, settings: settings)

      expect(hook).not_to be_valid
      expect(hook.errors[:base]).to include(I18n.t('errors.cloudflare.realtimekit.invalid_account_or_permissions'))
    end

    it 'prevents saving a RealtimeKit hook when the app is not found' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :app_not_found))

      hook = build(:integrations_hook, :dyte, account: account, settings: settings)

      expect(hook).not_to be_valid
      expect(hook.errors[:base]).to include(I18n.t('errors.cloudflare.realtimekit.app_not_found'))
    end

    it 'allows saving a RealtimeKit hook with valid credentials' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(true))

      hook = build(:integrations_hook, :dyte, account: account, settings: settings)

      expect(hook).to be_valid
    end

    it 'skips validation when an enabled RealtimeKit hook is saved without changing credentials' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(true))
      hook = create(:integrations_hook, :dyte, account: account, settings: settings)

      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :invalid_api_token))
      hook.settings['account_id'] = 'account_id'

      expect(hook.save).to be true
    end

    it 'validates when a disabled RealtimeKit hook is re-enabled' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(true))
      hook = create(:integrations_hook, :dyte, account: account, settings: settings)
      hook.update!(status: :disabled)

      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .with('account_id', 'app_id', 'api_token')
        .and_return(cloudflare_validator_result(false, :invalid_api_token))

      expect(hook.update(status: :enabled)).to be false
      expect(hook.errors[:base]).to include(I18n.t('errors.cloudflare.realtimekit.invalid_api_token'))
    end

    it 'skips validation for disabled RealtimeKit hooks' do
      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(true))
      hook = create(:integrations_hook, :dyte, account: account, settings: settings)

      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :invalid_api_token))
      hook.disable

      expect(hook.reload).to be_disabled
    end

    it 'allows disabling a persisted legacy Dyte hook without RealtimeKit credentials' do
      hook = build(:integrations_hook, :dyte, account: account, settings: { 'organization_id' => 'org_id', 'api_key' => 'dyte_api_key' })
      hook.save!(validate: false)

      allow(Integrations::Cloudflare::RealtimeKitCredentialsValidator).to receive(:validate)
        .and_return(cloudflare_validator_result(false, :invalid_api_token))

      expect(hook.disable).to be true
      expect(hook.reload).to be_disabled
    end

    it 'allows re-enabling a persisted legacy Dyte hook without RealtimeKit credential validation' do
      hook = build(:integrations_hook, :dyte, account: account, settings: { 'organization_id' => 'org_id', 'api_key' => 'dyte_api_key' })
      hook.save!(validate: false)
      hook.disable

      expect(Integrations::Cloudflare::RealtimeKitCredentialsValidator).not_to receive(:validate)

      expect(hook.update(status: :enabled)).to be true
      expect(hook.reload).to be_enabled
    end

    it 'validates settings when a legacy Dyte hook settings payload is changed' do
      hook = build(:integrations_hook, :dyte, account: account, settings: { 'organization_id' => 'org_id', 'api_key' => 'dyte_api_key' })
      hook.save!(validate: false)

      hook.settings = { 'account_id' => 'account_id' }

      expect(hook).not_to be_valid
      expect(hook.errors[:settings]).to include(': Invalid settings data')
    end

    it 'rejects new legacy Dyte hooks' do
      hook = build(:integrations_hook, :dyte,
                   account: account,
                   status: :disabled,
                   settings: { 'organization_id' => 'org_id', 'api_key' => 'dyte_api_key' })

      expect(hook).not_to be_valid
      expect(hook.errors[:settings]).to include(': Invalid settings data')
    end
  end

  def cloudflare_validator_result(success, error = nil)
    Integrations::Cloudflare::RealtimeKitCredentialsValidator::Result.new(success, error)
  end
end
