# frozen_string_literal: true

module Packages
  module Nuget
    class UpdatePackageFromMetadataService
      include Gitlab::Utils::StrongMemoize
      include ExclusiveLeaseGuard

      # used by ExclusiveLeaseGuard
      DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
      SYMBOL_PACKAGE_IDENTIFIER = 'SymbolsPackage'

      InvalidMetadataError = Class.new(StandardError)

      def initialize(package_file)
        @package_file = package_file
      end

      def execute
        raise InvalidMetadataError, 'package name and/or package version not found in metadata' unless valid_metadata?

        try_obtain_lease do
          @package_file.transaction do
            process_package_update
          end
        end
      rescue ActiveRecord::RecordInvalid => e
        raise InvalidMetadataError, e.message
      end

      private

      def process_package_update
        package_to_destroy = nil
        target_package = @package_file.package

        if existing_package
          package_to_destroy = @package_file.package
          target_package = existing_package
        else
          if symbol_package?
            raise InvalidMetadataError, 'symbol package is invalid, matching package does not exist'
          end

          update_linked_package
        end

        update_package(target_package)
        ::Packages::UpdatePackageFileService.new(@package_file, package_id: target_package.id, file_name: package_filename)
                                            .execute
        package_to_destroy&.destroy!
      end

      def update_package(package)
        return if symbol_package?

        ::Packages::Nuget::SyncMetadatumService
          .new(package, metadata.slice(:project_url, :license_url, :icon_url))
          .execute
        ::Packages::UpdateTagsService
          .new(package, package_tags)
          .execute
      rescue StandardError => e
        raise InvalidMetadataError, e.message
      end

      def valid_metadata?
        package_name.present? && package_version.present?
      end

      def link_to_existing_package
        package_to_destroy = @package_file.package
        # Updating package_id updates the path where the file is stored.
        # We must pass the file again so that CarrierWave can handle the update
        @package_file.update!(
          package_id: existing_package.id,
          file: @package_file.file
        )
        package_to_destroy.destroy!
        existing_package
      end

      def update_linked_package
        @package_file.package.update!(
          name: package_name,
          version: package_version,
          status: :default
        )

        ::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies)
                                                  .execute
        @package_file.package
      end

      def existing_package
        strong_memoize(:existing_package) do
          @package_file.project.packages
                               .nuget
                               .with_name(package_name)
                               .with_version(package_version)
                               .not_pending_destruction
                               .first
        end
      end

      def package_name
        metadata[:package_name]
      end

      def package_version
        metadata[:package_version]
      end

      def package_dependencies
        metadata.fetch(:package_dependencies, [])
      end

      def package_tags
        metadata.fetch(:package_tags, [])
      end

      def package_types
        metadata.fetch(:package_types, [])
      end

      def symbol_package?
        package_types.include?(SYMBOL_PACKAGE_IDENTIFIER)
      end

      def metadata
        strong_memoize(:metadata) do
          ::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute
        end
      end

      def package_filename
        "#{package_name.downcase}.#{package_version.downcase}.#{symbol_package? ? 'snupkg' : 'nupkg'}"
      end

      # used by ExclusiveLeaseGuard
      def lease_key
        package_id = existing_package ? existing_package.id : @package_file.package_id
        "packages:nuget:update_package_from_metadata_service:package:#{package_id}"
      end

      # used by ExclusiveLeaseGuard
      def lease_timeout
        DEFAULT_LEASE_TIMEOUT
      end
    end
  end
end
