# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

module VagrantPlugins
  module CommandServe
    module Service
      class ConfigService < ProtoService(SDK::ConfigService::Service)

        CONFIG_LOCATIONS = [
          :config,
          :provider_configs,
          :provisioner_configs,
          :push_configs,
        ]

        def register(req, ctx)
          with_plugin(ctx, CONFIG_LOCATIONS, broker: broker) do |plugin|
            resp = SDK::Config::RegisterResponse.new
            Vagrant.plugin("2").local_manager.registered.each do |plg|
              plg.components.configs.each do |scope, registry|
                registry.each do |ident, val|
                  if plugin == val
                    resp.scope = scope unless scope == :top
                    resp.identifier = ident
                    break
                  end
                end
              end
            end
            resp
          end
        end

        def init_spec(*_)
          funcspec(
            args: [
              SDK::Args::ConfigData,
            ],
            result: SDK::Config::InitResponse
          )
        end

        def init(req, ctx)
          with_plugin(ctx, :config, broker: broker) do |plugin|
            config_data_p = mapper.unfuncspec(req.args.first)
            config_data = mapper.map(config_data_p.data)
            instance = plugin.new

            config_data.each do |key, value|
              key = key.downcase.to_sym
              if instance.respond_to?("#{key}=".to_sym)
                instance.send("#{key}=".to_sym, value)
              elsif instance.respond_to?(key)
                instance.send(key, value)
              else
                logger.warn("unknown config key to apply: class: #{plugin} key: #{key}")
              end
            end

            SDK::Config::InitResponse.new(data: mapper.map(instance, to: SDK::Args::ConfigData))
          end
        end

        def struct_spec(*_)
          funcspec(
            args: [
            ],
            result: SDK::Config::StructResponse
          )
        end

        def struct(req, ctx)
          SDK::Config::StructResponse.new(raw: true)
        end

        def merge_spec(*_)
          funcspec(
            args: [
              SDK::Config::Merge,
            ],
            result: SDK::Args::ConfigData,
          )
        end

        def merge(req, ctx)
          with_plugin(ctx, :config, broker: broker) do |plugin|
            m = mapper.unfuncspec(req.args.first)
            base = mapper.map(m.base, to: plugin)
            overlay = mapper.map(m.overlay, to: plugin)

            mapper.map(base.merge(overlay), to: SDK::Args::ConfigData)
          end
        end

        def finalize_spec(*_)
          funcspec(
            args: [
              SDK::Config::Finalize,
            ],
            result: SDK::Args::ConfigData
          )
        end

        def finalize(req, ctx)
          with_plugin(ctx, CONFIG_LOCATIONS, broker: broker) do |plugin|
            # Extract the proto from the funcspec
            f = mapper.unfuncspec(req.args.first)
            cproto = f.config

            # If the config data does not include a source class, we treat
            # the request as simply wanting the default finalized data
            if cproto.source.name.to_s.empty?
              config = plugin.new
            else
              config = mapper.map(cproto, to: plugin)
            end

            config.finalize!
            # This is just a marker for debugging that we were
            # responsible for the finalization
            config.instance_variable_set("@__service_finalized", true)

            SDK::Config::FinalizeResponse.new(data: mapper.map(config, to: SDK::Args::ConfigData))
          end
        end
      end
    end
  end
end
