# frozen_string_literal: true

FactoryBot.define do
  factory :design, traits: [:has_internal_id], class: 'DesignManagement::Design' do
    issue { association(:issue) }
    project { issue&.project || association(:project) }
    sequence(:filename) { |n| "homescreen-#{n}.jpg" }

    transient do
      author { issue.author }
    end

    trait :importing do
      issue { nil }

      importing { true }
    end

    trait :with_relative_position do
      sequence(:relative_position) { |n| n * 1000 }
    end

    create_versions = ->(design, evaluator, commit_version) do # rubocop:disable RSpec/FactoryBot/LocalStaticAssignment
      unless evaluator.versions_count == 0
        project = design.project
        issue = design.issue
        repository = project.design_repository
        repository.create_if_not_exists
        dv_table_name = DesignManagement::Action.table_name
        updates = [0, evaluator.versions_count - (evaluator.deleted ? 2 : 1)].max

        run_action = ->(action) do
          sha = commit_version[action]
          version = DesignManagement::Version.new(sha: sha, issue: issue, author: evaluator.author)
          version.save!(validate: false) # We need it to have an ID, validate later
          ApplicationRecord.legacy_bulk_insert(dv_table_name, [action.row_attrs(version)]) # rubocop:disable Gitlab/BulkInsert
        end

        # always a creation
        run_action[DesignManagement::DesignAction.new(design, :create, evaluator.file)]

        # 0 or more updates
        updates.times do
          run_action[DesignManagement::DesignAction.new(design, :update, evaluator.file)]
        end

        # and maybe a deletion
        run_action[DesignManagement::DesignAction.new(design, :delete)] if evaluator.deleted
      end

      design.clear_version_cache
    end

    # Use this trait to build designs that are backed by Git LFS, committed
    # to the repository, and with an LfsObject correctly created for it.
    trait :with_lfs_file do
      with_file

      transient do
        raw_file { fixture_file_upload('spec/fixtures/dk.png', 'image/png') }
        lfs_pointer { Gitlab::Git::LfsPointerFile.new(SecureRandom.random_bytes) }
        file { lfs_pointer.pointer }
      end

      after :create do |design, evaluator|
        lfs_object = create(:lfs_object, file: evaluator.raw_file, oid: evaluator.lfs_pointer.sha256, size: evaluator.lfs_pointer.size)
        create(:lfs_objects_project, project: design.project, lfs_object: lfs_object, repository_type: :design)
      end
    end

    # Use this trait if you want versions in a particular history, but don't
    # want to pay for gitaly calls.
    trait :with_versions do
      transient do
        deleted { false }
        versions_count { 1 }
        sequence(:file) { |n| "some-file-content-#{n}" }
      end

      after :create do |design, evaluator|
        counter = (1..).lazy

        # Just produce a SHA by hashing the action and a monotonic counter
        commit_version = ->(action) do
          Digest::SHA1.hexdigest("#{action.gitaly_action}.#{counter.next}")
        end

        create_versions[design, evaluator, commit_version]
      end
    end

    # Use this trait to build designs that have commits in the repository
    # and files that can be retrieved.
    trait :with_file do
      transient do
        deleted { false }
        file { File.join(Rails.root, 'spec/fixtures/dk.png') }
        versions_count { 1 }
        versions_sha { nil }
      end

      after :create do |design, evaluator|
        project = design.project
        repository = project.design_repository

        commit_version = ->(action) do
          return evaluator.versions_sha if evaluator.versions_sha

          repository.commit_files(
            evaluator.author,
            branch_name: 'master',
            message: "#{action.action} for #{design.filename}",
            actions: [action.gitaly_action]
          )
        end

        create_versions[design, evaluator, commit_version]
      end
    end

    trait :with_smaller_image_versions do
      with_lfs_file

      after :create do |design|
        design.versions.each { |v| DesignManagement::GenerateImageVersionsService.new(v).execute }
      end
    end
  end
end
