/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package config

import (
	"fmt"
	"strings"
)

import (
	"github.com/creasty/defaults"

	"github.com/dubbogo/gost/log/logger"

	tripleConstant "github.com/dubbogo/triple/pkg/common/constant"

	perrors "github.com/pkg/errors"
)

import (
	"dubbo.apache.org/dubbo-go/v3/common"
	"dubbo.apache.org/dubbo-go/v3/common/constant"
	aslimiter "dubbo.apache.org/dubbo-go/v3/filter/adaptivesvc/limiter"
)

// ProviderConfig is the default configuration of service provider
type ProviderConfig struct {
	Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"`
	// Deprecated Register whether registration is required
	Register bool `yaml:"register" json:"register" property:"register"`
	// RegistryIDs is registry ids list
	RegistryIDs []string `yaml:"registry-ids" json:"registry-ids" property:"registry-ids"`
	// protocol
	ProtocolIDs []string `yaml:"protocol-ids" json:"protocol-ids" property:"protocol-ids"`
	// TracingKey is tracing ids list
	TracingKey string `yaml:"tracing-key" json:"tracing-key" property:"tracing-key"`
	// Services services
	Services     map[string]*ServiceConfig `yaml:"services" json:"services,omitempty" property:"services"`
	ProxyFactory string                    `default:"default" yaml:"proxy" json:"proxy,omitempty" property:"proxy"`
	FilterConf   any                       `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf"`
	ConfigType   map[string]string         `yaml:"config_type" json:"config_type,omitempty" property:"config_type"`
	// adaptive service
	AdaptiveService        bool `yaml:"adaptive-service" json:"adaptive-service" property:"adaptive-service"`
	AdaptiveServiceVerbose bool `yaml:"adaptive-service-verbose" json:"adaptive-service-verbose" property:"adaptive-service-verbose"`

	rootConfig *RootConfig
}

func (ProviderConfig) Prefix() string {
	return constant.ProviderConfigPrefix
}

func (c *ProviderConfig) check() error {
	if err := defaults.Set(c); err != nil {
		return err
	}
	return verify(c)
}

func (c *ProviderConfig) Init(rc *RootConfig) error {
	if c == nil {
		return nil
	}
	buildDebugMsg := func() string {
		if len(c.Services) == 0 {
			return "empty"
		}
		providerNames := make([]string, 0, len(c.Services))
		for k := range c.Services {
			providerNames = append(providerNames, k)
		}
		return strings.Join(providerNames, ", ")
	}
	logger.Debugf("Registered provider services are %v", buildDebugMsg())

	c.RegistryIDs = translateIds(c.RegistryIDs)
	if len(c.RegistryIDs) <= 0 {
		c.RegistryIDs = rc.getRegistryIds()
	}
	c.ProtocolIDs = translateIds(c.ProtocolIDs)

	if c.TracingKey == "" && len(rc.Tracing) > 0 {
		for k := range rc.Tracing {
			c.TracingKey = k
			break
		}
	}
	for key, serviceConfig := range c.Services {
		if serviceConfig.Interface == "" {
			service := GetProviderService(key)
			// try to use interface name defined by pb
			supportPBPackagerNameService, ok := service.(common.TriplePBService)
			if !ok {
				logger.Errorf("Service with reference = %s is not support read interface name from it."+
					"Please run go install github.com/dubbogo/dubbogo-cli/cmd/protoc-gen-go-triple@latest to update your "+
					"protoc-gen-go-triple and re-generate your pb file again."+
					"If you are not using pb serialization, please set 'interface' field in service config.", key)
				continue
			} else {
				// use interface name defined by pb
				serviceConfig.Interface = supportPBPackagerNameService.XXX_InterfaceName()
			}
		}
		if err := serviceConfig.Init(rc); err != nil {
			return err
		}

		serviceConfig.adaptiveService = c.AdaptiveService
	}

	for k, v := range rc.Protocols {
		if v.Name == tripleConstant.TRIPLE {
			// Auto create grpc based health check service.
			healthService := NewServiceConfigBuilder().
				SetProtocolIDs(k).
				SetNotRegister(true).
				SetInterface(constant.HealthCheckServiceInterface).
				Build()
			if err := healthService.Init(rc); err != nil {
				return err
			}
			c.Services[constant.HealthCheckServiceTypeName] = healthService

			// Auto create reflection service configure only when provider with triple service is configured.
			tripleReflectionService := NewServiceConfigBuilder().
				SetProtocolIDs(k).
				SetNotRegister(true).
				SetInterface(constant.ReflectionServiceInterface).
				Build()
			if err := tripleReflectionService.Init(rc); err != nil {
				return err
			}
			// Maybe only register once, If setting this service, break from traversing Protocols.
			c.Services[constant.ReflectionServiceTypeName] = tripleReflectionService
			break
		}
	}

	if err := c.check(); err != nil {
		return err
	}
	// enable adaptive service verbose
	if c.AdaptiveServiceVerbose {
		if !c.AdaptiveService {
			return perrors.Errorf("The adaptive service is disabled, " +
				"adaptive service verbose should be disabled either.")
		}
		logger.Infof("adaptive service verbose is enabled.")
		logger.Debugf("debug-level info could be shown.")
		aslimiter.Verbose = true
	}
	return nil
}

func (c *ProviderConfig) Load() {
	for registeredTypeName, service := range GetProviderServiceMap() {
		serviceConfig, ok := c.Services[registeredTypeName]
		if !ok {
			if registeredTypeName == constant.ReflectionServiceTypeName ||
				registeredTypeName == constant.HealthCheckServiceTypeName {
				// do not auto generate reflection or health check server's configuration.
				continue
			}
			// service doesn't config in config file, create one with default
			supportPBPackagerNameService, ok := service.(common.TriplePBService)
			if !ok {
				logger.Warnf(
					"The provider service %s is ignored: neither the config is found, nor it is a valid Triple service.",
					registeredTypeName)
				continue
			}
			serviceConfig = NewServiceConfigBuilder().Build()
			// use interface name defined by pb
			serviceConfig.Interface = supportPBPackagerNameService.XXX_InterfaceName()
			if err := serviceConfig.Init(rootConfig); err != nil {
				logger.Errorf("Service with registeredTypeName = %s init failed with error = %#v", registeredTypeName, err)
			}
			serviceConfig.adaptiveService = c.AdaptiveService
		}
		serviceConfig.id = registeredTypeName
		serviceConfig.Implement(service)
		if err := serviceConfig.Export(); err != nil {
			logger.Errorf(fmt.Sprintf("service with registeredTypeName = %s export failed! err: %#v", registeredTypeName, err))
		}
	}
}

// newEmptyProviderConfig returns ProviderConfig with default ApplicationConfig
func newEmptyProviderConfig() *ProviderConfig {
	newProviderConfig := &ProviderConfig{
		Services:    make(map[string]*ServiceConfig),
		RegistryIDs: make([]string, 8),
		ProtocolIDs: make([]string, 8),
	}
	return newProviderConfig
}

type ProviderConfigBuilder struct {
	providerConfig *ProviderConfig
}

func NewProviderConfigBuilder() *ProviderConfigBuilder {
	return &ProviderConfigBuilder{providerConfig: newEmptyProviderConfig()}
}

func (pcb *ProviderConfigBuilder) SetFilter(filter string) *ProviderConfigBuilder {
	pcb.providerConfig.Filter = filter
	return pcb
}

func (pcb *ProviderConfigBuilder) SetRegister(register bool) *ProviderConfigBuilder {
	pcb.providerConfig.Register = register
	return pcb
}

func (pcb *ProviderConfigBuilder) SetRegistryIDs(RegistryIDs ...string) *ProviderConfigBuilder {
	pcb.providerConfig.RegistryIDs = RegistryIDs
	return pcb
}

func (pcb *ProviderConfigBuilder) SetServices(services map[string]*ServiceConfig) *ProviderConfigBuilder {
	pcb.providerConfig.Services = services
	return pcb
}

func (pcb *ProviderConfigBuilder) AddService(serviceID string, serviceConfig *ServiceConfig) *ProviderConfigBuilder {
	if pcb.providerConfig.Services == nil {
		pcb.providerConfig.Services = make(map[string]*ServiceConfig)
	}
	pcb.providerConfig.Services[serviceID] = serviceConfig
	return pcb
}

func (pcb *ProviderConfigBuilder) SetProxyFactory(proxyFactory string) *ProviderConfigBuilder {
	pcb.providerConfig.ProxyFactory = proxyFactory
	return pcb
}

func (pcb *ProviderConfigBuilder) SetFilterConf(filterConf any) *ProviderConfigBuilder {
	pcb.providerConfig.FilterConf = filterConf
	return pcb
}

func (pcb *ProviderConfigBuilder) SetConfigType(configType map[string]string) *ProviderConfigBuilder {
	pcb.providerConfig.ConfigType = configType
	return pcb
}

func (pcb *ProviderConfigBuilder) AddConfigType(key, value string) *ProviderConfigBuilder {
	if pcb.providerConfig.ConfigType == nil {
		pcb.providerConfig.ConfigType = make(map[string]string)
	}
	pcb.providerConfig.ConfigType[key] = value
	return pcb
}

func (pcb *ProviderConfigBuilder) SetRootConfig(rootConfig *RootConfig) *ProviderConfigBuilder {
	pcb.providerConfig.rootConfig = rootConfig
	return pcb
}

func (pcb *ProviderConfigBuilder) Build() *ProviderConfig {
	return pcb.providerConfig
}
