# frozen_string_literal: true

require 'rails_helper'

# Define a dummy GridWeaponBlueprint if it is not already defined.
class GridWeaponBlueprint; end unless defined?(GridWeaponBlueprint)

RSpec.describe GridWeapon, type: :model do
  it { is_expected.to belong_to(:weapon) }
  it { is_expected.to belong_to(:party) }
  it { is_expected.to belong_to(:weapon_key1).optional }
  it { is_expected.to belong_to(:weapon_key2).optional }
  it { is_expected.to belong_to(:weapon_key3).optional }
  it { is_expected.to belong_to(:weapon_key4).optional }
  it { is_expected.to belong_to(:awakening).optional }
  it { is_expected.to belong_to(:collection_weapon).optional }

  # Setup common test objects using FactoryBot.
  let(:party) { create(:party) }
  let(:default_series) { create(:weapon_series, extra: false) }
  let(:weapon) { create(:weapon, limit: false, weapon_series: default_series) }
  let(:grid_weapon) do
    build(:grid_weapon,
          party: party,
          weapon: weapon,
          position: 0,
          uncap_level: 3,
          transcendence_step: 0)
  end

  describe 'Validations' do
    context 'Presence validations' do
      it 'requires a party' do
        grid_weapon.party = nil
        grid_weapon.validate
        error_message = grid_weapon.errors[:party].join
        expect(error_message).to include('must exist')
      end
    end

    context 'Custom validations' do
      describe '#compatible_with_position' do
        context 'when position is within extra positions [9, 10, 11]' do
          before { grid_weapon.position = 9 }

          context 'and weapon series is NOT in allowed extra series' do
            # default_series has extra: false, so this should fail
            it 'adds an error on :series' do
              grid_weapon.validate
              expect(grid_weapon.errors[:series]).to include('must be compatible with position')
            end
          end

          context 'and weapon series is in allowed extra series' do
            let(:extra_series) { create(:weapon_series, extra: true) }
            let(:extra_weapon) { create(:weapon, limit: false, weapon_series: extra_series) }

            before { grid_weapon.weapon = extra_weapon }

            it 'is valid with respect to position compatibility' do
              grid_weapon.validate
              expect(grid_weapon.errors[:series]).to be_empty
            end
          end
        end

        context 'when position is not in extra positions' do
          before { grid_weapon.position = 2 }
          it 'does not add an error on :series' do
            grid_weapon.validate
            expect(grid_weapon.errors[:series]).to be_empty
          end
        end
      end

      describe '#no_conflicts' do
        context 'when there is a conflicting grid weapon in the party' do
          let(:limited_series) { create(:weapon_series) }
          let(:limited_weapon) { create(:weapon, limit: true, weapon_series: limited_series) }

          before do
            # Create an existing grid weapon in the party using that limited weapon.
            create(:grid_weapon, party: party, weapon: limited_weapon, position: 1)
            # Set up grid_weapon to use the same limited weapon in a different position.
            grid_weapon.weapon = limited_weapon
            grid_weapon.position = 2
          end

          it 'adds an error on :series about conflicts' do
            grid_weapon.validate
            expect(grid_weapon.errors[:series]).to include('must not conflict with existing weapons')
          end
        end

        context 'when there is no conflicting grid weapon' do
          it 'has no conflict errors' do
            grid_weapon.validate
            expect(grid_weapon.errors[:series]).to be_empty
          end
        end
      end
    end
  end

  describe 'Callbacks' do
    context 'before_save :mainhand?' do
      it 'sets mainhand to true if position is -1' do
        grid_weapon.position = -1
        grid_weapon.save!
        expect(grid_weapon.mainhand).to be true
      end

      it 'sets mainhand to false if position is not -1' do
        grid_weapon.position = 0
        grid_weapon.save!
        expect(grid_weapon.mainhand).to be false
      end
    end
  end

  describe '#weapon_keys' do
    it 'returns an array of associated weapon keys, omitting nils' do
      # Create two dummy weapon keys using the factory.
      weapon_key1 = create(:weapon_key)
      weapon_key2 = create(:weapon_key)
      grid_weapon.weapon_key1 = weapon_key1
      grid_weapon.weapon_key2 = weapon_key2
      grid_weapon.weapon_key3 = nil
      expect(grid_weapon.weapon_keys).to match_array([weapon_key1, weapon_key2])
    end
  end

  describe '#blueprint' do
    it 'returns the GridWeaponBlueprint constant' do
      expect(grid_weapon.blueprint).to eq(GridWeaponBlueprint)
    end
  end

  describe 'Collection Sync' do
    let(:user) { create(:user) }
    let(:collection_weapon) do
      create(:collection_weapon,
             user: user,
             weapon: weapon,
             uncap_level: 5,
             transcendence_step: 0,
             element: 2)
    end

    describe '#sync_from_collection!' do
      context 'when collection_weapon is linked' do
        let(:linked_grid_weapon) do
          create(:grid_weapon,
                 party: party,
                 weapon: weapon,
                 position: 0,
                 collection_weapon: collection_weapon,
                 uncap_level: 3)
        end

        it 'copies customizations from collection' do
          expect(linked_grid_weapon.sync_from_collection!).to be true
          linked_grid_weapon.reload

          expect(linked_grid_weapon.uncap_level).to eq(5)
          expect(linked_grid_weapon.element).to eq(2)
        end
      end

      context 'when no collection_weapon is linked' do
        it 'returns false' do
          expect(grid_weapon.sync_from_collection!).to be false
        end
      end
    end

    describe '#out_of_sync?' do
      context 'when collection_weapon is linked' do
        let(:linked_grid_weapon) do
          create(:grid_weapon,
                 party: party,
                 weapon: weapon,
                 position: 0,
                 collection_weapon: collection_weapon,
                 uncap_level: 3)
        end

        it 'returns true when values differ' do
          expect(linked_grid_weapon.out_of_sync?).to be true
        end

        it 'returns false after sync' do
          linked_grid_weapon.sync_from_collection!
          expect(linked_grid_weapon.out_of_sync?).to be false
        end
      end

      context 'when no collection_weapon is linked' do
        it 'returns false' do
          expect(grid_weapon.out_of_sync?).to be false
        end
      end
    end
  end
end
