require 'rails_helper'

RSpec.describe 'TagVersions API', type: :request do
  let(:member) { create(:user, :member, name: 'version member') }

  let!(:tag) { create(:tag, name: 'tag_versions_target', category: :general) }
  let!(:other_tag) { create(:tag, name: 'tag_versions_other', category: :general) }

  let!(:parent_shared) { create(:tag, name: 'parent_shared', category: :general) }
  let!(:parent_old) { create(:tag, name: 'parent_old', category: :general) }
  let!(:parent_new) { create(:tag, name: 'parent_new', category: :general) }
  let!(:other_parent) { create(:tag, name: 'other_parent', category: :general) }

  let(:t_v1) { Time.zone.local(2020, 1, 1, 12, 0, 0) }
  let(:t_v2) { Time.zone.local(2020, 1, 2, 12, 0, 0) }
  let(:t_other) { Time.zone.local(2020, 1, 3, 12, 0, 0) }

  def create_tag_version!(
    tag:,
    version_no:,
    event_type:,
    name:,
    category:,
    deprecated_at: nil,
    aliases: [],
    parent_tags: [],
    created_by_user:,
    created_at:
  )
    version =
        TagVersion.create!(
            tag: tag,
            version_no: version_no,
            event_type: event_type,
            name: name,
            category: category,
            deprecated_at: deprecated_at,
            aliases: Array(aliases).join(' '),
            parent_tag_ids: Array(parent_tags).map(&:id).join(' '),
            created_by_user: created_by_user,
            created_at: created_at)

    tag.update_columns(version_no: version_no) if tag.has_attribute?(:version_no)
    tag.version_no = version_no if tag.respond_to?(:version_no=)

    version
  end

  let!(:v1) do
    create_tag_version!(
      tag: tag,
      version_no: 1,
      event_type: 'create',
      name: 'old_tag_name',
      category: 'general',
      aliases: ['alias_shared', 'alias_old'],
      parent_tags: [parent_shared, parent_old],
      created_by_user: member,
      created_at: t_v1
    )
  end

  let!(:v2) do
    create_tag_version!(
      tag: tag,
      version_no: 2,
      event_type: 'update',
      name: 'new_tag_name',
      category: 'meme',
      deprecated_at: t_v2,
      aliases: ['alias_shared', 'alias_new'],
      parent_tags: [parent_shared, parent_new],
      created_by_user: member,
      created_at: t_v2
    )
  end

  let!(:other_v1) do
    create_tag_version!(
      tag: other_tag,
      version_no: 1,
      event_type: 'create',
      name: 'other_tag_name',
      category: 'general',
      aliases: ['other_alias'],
      parent_tags: [other_parent],
      created_by_user: member,
      created_at: t_other
    )
  end

  describe 'GET /tags/versions' do
    it 'returns all versions in reverse chronological order when id is omitted' do
      get '/tags/versions'

      expect(response).to have_http_status(:ok)
      expect(json).to include('versions', 'count')
      expect(json.fetch('count')).to eq(3)

      versions = json.fetch('versions')

      expect(versions.map { |v| [v['tag_id'], v['version_no']] }).to eq([
        [other_tag.id, 1],
        [tag.id, 2],
        [tag.id, 1]
      ])
    end

    it 'returns versions for the specified tag with diffs' do
      get '/tags/versions', params: { id: tag.id }

      expect(response).to have_http_status(:ok)
      expect(json).to include('versions', 'count')
      expect(json.fetch('count')).to eq(2)

      versions = json.fetch('versions')
      expect(versions.map { |v| v['tag_id'] }.uniq).to eq([tag.id])
      expect(versions.map { |v| v['version_no'] }).to eq([2, 1])

      latest = versions.first
      expect(latest).to include(
        'tag_id' => tag.id,
        'version_no' => 2,
        'event_type' => 'update',
        'created_by_user' => {
          'id' => member.id,
          'name' => member.name
        }
      )

      expect(latest.fetch('name')).to eq(
        'current' => 'new_tag_name',
        'prev' => 'old_tag_name'
      )
      expect(latest.fetch('category')).to eq(
        'current' => 'meme',
        'prev' => 'general'
      )
      expect(latest.fetch('deprecated_at')).to eq(
        'current' => t_v2.iso8601,
        'prev' => nil
      )
      expect(latest.fetch('aliases')).to include(
        { 'name' => 'alias_shared', 'type' => 'context' },
        { 'name' => 'alias_new', 'type' => 'added' },
        { 'name' => 'alias_old', 'type' => 'removed' }
      )
      expect(latest.fetch('parent_tags')).to include(
        a_hash_including(
          'type' => 'context',
          'tag' => a_hash_including(
            'id' => parent_shared.id
          )
        ),
        a_hash_including(
          'type' => 'added',
          'tag' => a_hash_including(
            'id' => parent_new.id
          )
        ),
        a_hash_including(
          'type' => 'removed',
          'tag' => a_hash_including(
            'id' => parent_old.id
          )
        )
      )
      expect(latest.fetch('created_at')).to eq(t_v2.iso8601)

      first = versions.second
      expect(first).to include(
        'tag_id' => tag.id,
        'version_no' => 1,
        'event_type' => 'create',
        'created_by_user' => {
          'id' => member.id,
          'name' => member.name
        }
      )
      expect(first.fetch('name')).to eq(
        'current' => 'old_tag_name',
        'prev' => nil
      )
      expect(first.fetch('category')).to eq(
        'current' => 'general',
        'prev' => nil
      )
      expect(first.fetch('deprecated_at')).to eq(
        'current' => nil,
        'prev' => nil
      )
      expect(first.fetch('aliases')).to include(
        { 'name' => 'alias_shared', 'type' => 'added' },
        { 'name' => 'alias_old', 'type' => 'added' }
      )
      expect(first.fetch('parent_tags')).to include(
        a_hash_including(
          'type' => 'added',
          'tag' => a_hash_including(
            'id' => parent_shared.id
          )
        ),
        a_hash_including(
          'type' => 'added',
          'tag' => a_hash_including(
            'id' => parent_old.id
          )
        )
      )
      expect(first.fetch('created_at')).to eq(t_v1.iso8601)
    end

    it 'returns empty when the specified tag has no versions' do
      fresh_tag = create(:tag, name: 'no_versions_tag', category: :general)

      get '/tags/versions', params: { id: fresh_tag.id }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('versions')).to eq([])
      expect(json.fetch('count')).to eq(0)
    end

    it 'clamps page and limit to at least 1' do
      get '/tags/versions', params: { id: tag.id, page: 0, limit: 0 }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('count')).to eq(2)

      versions = json.fetch('versions')
      expect(versions.size).to eq(1)
      expect(versions.first['version_no']).to eq(2)
    end

    it 'does not create tag versions by wiki updates when tag has no versions yet' do
      wiki_tag_name = TagName.create!(name: 'tag_versions_from_wiki')
      wiki_tag = Tag.create!(tag_name: wiki_tag_name, category: :general)

      wiki_page =
          Wiki::Commit.create_content!(
              tag_name: wiki_tag_name,
              body: 'before',
              created_by_user: member,
              message: 'init')

      Wiki::Commit.content!(
          page: wiki_page,
          body: 'after',
          created_user: member,
          message: 'edit',
          base_revision_id: wiki_page.current_revision.id)

      get '/tags/versions', params: { id: wiki_tag.id }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('versions')).to eq([])
      expect(json.fetch('count')).to eq(0)
    end
  end
end
