/*
 * 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 dubbo

import (
	"strconv"
	"time"
)

import (
	log "github.com/dubbogo/gost/log/logger"
)

import (
	"dubbo.apache.org/dubbo-go/v3/common/constant"
	"dubbo.apache.org/dubbo-go/v3/config"
	"dubbo.apache.org/dubbo-go/v3/config_center"
	"dubbo.apache.org/dubbo-go/v3/global"
	"dubbo.apache.org/dubbo-go/v3/graceful_shutdown"
	"dubbo.apache.org/dubbo-go/v3/logger"
	"dubbo.apache.org/dubbo-go/v3/metadata"
	"dubbo.apache.org/dubbo-go/v3/metrics"
	"dubbo.apache.org/dubbo-go/v3/otel/trace"
	"dubbo.apache.org/dubbo-go/v3/protocol"
	"dubbo.apache.org/dubbo-go/v3/registry"
	"dubbo.apache.org/dubbo-go/v3/tls"
)

type InstanceOptions struct {
	Application    *global.ApplicationConfig         `validate:"required" yaml:"application" json:"application,omitempty" property:"application"`
	Protocols      map[string]*global.ProtocolConfig `validate:"required" yaml:"protocols" json:"protocols" property:"protocols"`
	Registries     map[string]*global.RegistryConfig `yaml:"registries" json:"registries" property:"registries"`
	ConfigCenter   *global.CenterConfig              `yaml:"config-center" json:"config-center,omitempty"`
	MetadataReport *global.MetadataReportConfig      `yaml:"metadata-report" json:"metadata-report,omitempty" property:"metadata-report"`
	Provider       *global.ProviderConfig            `yaml:"provider" json:"provider" property:"provider"`
	Consumer       *global.ConsumerConfig            `yaml:"consumer" json:"consumer" property:"consumer"`
	Metrics        *global.MetricsConfig             `yaml:"metrics" json:"metrics,omitempty" property:"metrics"`
	Otel           *global.OtelConfig                `yaml:"otel" json:"otel,omitempty" property:"otel"`
	Logger         *global.LoggerConfig              `yaml:"logger" json:"logger,omitempty" property:"logger"`
	Shutdown       *global.ShutdownConfig            `yaml:"shutdown" json:"shutdown,omitempty" property:"shutdown"`
	// todo(DMwangnima): router feature would be supported in the future
	//Router              []*RouterConfig                   `yaml:"router" json:"router,omitempty" property:"router"`
	EventDispatcherType string                 `default:"direct" yaml:"event-dispatcher-type" json:"event-dispatcher-type,omitempty"`
	CacheFile           string                 `yaml:"cache_file" json:"cache_file,omitempty" property:"cache_file"`
	Custom              *global.CustomConfig   `yaml:"custom" json:"custom,omitempty" property:"custom"`
	Profiles            *global.ProfilesConfig `yaml:"profiles" json:"profiles,omitempty" property:"profiles"`
	TLSConfig           *global.TLSConfig      `yaml:"tls_config" json:"tls_config,omitempty" property:"tls_config"`
}

func defaultInstanceOptions() *InstanceOptions {
	return &InstanceOptions{
		Application:    global.DefaultApplicationConfig(),
		Protocols:      make(map[string]*global.ProtocolConfig),
		Registries:     make(map[string]*global.RegistryConfig),
		ConfigCenter:   global.DefaultCenterConfig(),
		MetadataReport: global.DefaultMetadataReportConfig(),
		Provider:       global.DefaultProviderConfig(),
		Consumer:       global.DefaultConsumerConfig(),
		Metrics:        global.DefaultMetricsConfig(),
		Otel:           global.DefaultOtelConfig(),
		Logger:         global.DefaultLoggerConfig(),
		Shutdown:       global.DefaultShutdownConfig(),
		Custom:         global.DefaultCustomConfig(),
		Profiles:       global.DefaultProfilesConfig(),
		TLSConfig:      global.DefaultTLSConfig(),
	}
}

func (rc *InstanceOptions) init(opts ...InstanceOption) error {
	for _, opt := range opts {
		opt(rc)
	}

	// remaining procedure is like RootConfig.Init() without RootConfig.Start()
	// tasks of RootConfig.Start() would be decomposed to Client and Server
	rcCompat := compatRootConfig(rc)

	if err := rcCompat.Logger.Init(); err != nil { // init default logger
		return err
	}
	if err := rcCompat.ConfigCenter.Init(rcCompat); err != nil {
		log.Infof("[Config Center] Config center doesn't start")
		log.Debugf("config center doesn't start because %s", err)
	} else {
		compatInstanceOptions(rcCompat, rc)
		if err = rcCompat.Logger.Init(); err != nil { // init logger using config from config center again
			return err
		}
	}

	if err := rcCompat.Application.Init(); err != nil {
		return err
	}

	// init user define
	if err := rcCompat.Custom.Init(); err != nil {
		return err
	}

	// init protocol
	for _, protocolConfig := range rcCompat.Protocols {
		if err := protocolConfig.Init(); err != nil {
			return err
		}
	}

	// init registry
	registries := rcCompat.Registries

	for _, reg := range registries {
		if err := reg.Init(); err != nil {
			return err
		}
	}

	if err := rcCompat.Metrics.Init(rcCompat); err != nil {
		return err
	}
	if err := rcCompat.Otel.Init(rcCompat.Application); err != nil {
		return err
	}

	routers := rcCompat.Router
	if len(routers) > 0 {
		for _, r := range routers {
			if err := r.Init(); err != nil {
				return err
			}
		}
		rcCompat.Router = routers
	}

	// provider、consumer must last init
	if err := rcCompat.Provider.Init(rcCompat); err != nil {
		return err
	}
	if err := rcCompat.Consumer.Init(rcCompat); err != nil {
		return err
	}
	if err := rcCompat.Shutdown.Init(); err != nil {
		return err
	}
	config.SetRootConfig(*rcCompat)

	if err := rc.initMetadataReport(); err != nil {
		return err
	}
	if err := metadata.InitRegistryMetadataReport(rc.Registries); err != nil {
		return err
	}

	compatInstanceOptions(rcCompat, rc) // overrider options config because some config are changed after init

	return nil
}

func (rc *InstanceOptions) initMetadataReport() error {
	if rc.MetadataReport.Address != "" {
		opts, err := reportConfigToReportOptions(rc.MetadataReport)
		if err != nil {
			return err
		}
		if err := opts.Init(); err != nil {
			return err
		}
	}
	return nil
}

func reportConfigToReportOptions(mc *global.MetadataReportConfig) (*metadata.ReportOptions, error) {
	opts := metadata.NewReportOptions(
		metadata.WithRegistryId(constant.DefaultKey),
		metadata.WithProtocol(mc.Protocol),
		metadata.WithAddress(mc.Address),
		metadata.WithUsername(mc.Username),
		metadata.WithPassword(mc.Password),
		metadata.WithGroup(mc.Group),
		metadata.WithNamespace(mc.Namespace),
		metadata.WithParams(mc.Params),
	)
	if mc.Timeout != "" {
		timeout, err := time.ParseDuration(mc.Timeout)
		if err != nil {
			return nil, err
		}
		metadata.WithTimeout(timeout)(opts)
	}
	return opts, nil
}

func (rc *InstanceOptions) Prefix() string {
	return constant.Dubbo
}

func (rc *InstanceOptions) CloneApplication() *global.ApplicationConfig {
	if rc.Application == nil {
		return nil
	}
	return rc.Application.Clone()
}

func (rc *InstanceOptions) CloneProtocols() map[string]*global.ProtocolConfig {
	if rc.Protocols == nil {
		return nil
	}
	protocols := make(map[string]*global.ProtocolConfig, len(rc.Protocols))
	for k, v := range rc.Protocols {
		protocols[k] = v.Clone()
	}
	return protocols
}

func (rc *InstanceOptions) CloneRegistries() map[string]*global.RegistryConfig {
	if rc.Registries == nil {
		return nil
	}
	registries := make(map[string]*global.RegistryConfig, len(rc.Registries))
	for k, v := range rc.Registries {
		registries[k] = v.Clone()
	}
	return registries
}

func (rc *InstanceOptions) CloneConfigCenter() *global.CenterConfig {
	if rc.ConfigCenter == nil {
		return nil
	}
	return rc.ConfigCenter.Clone()
}

func (rc *InstanceOptions) CloneMetadataReport() *global.MetadataReportConfig {
	if rc.MetadataReport == nil {
		return nil
	}
	return rc.MetadataReport.Clone()
}

func (rc *InstanceOptions) CloneProvider() *global.ProviderConfig {
	if rc.Provider == nil {
		return nil
	}
	return rc.Provider.Clone()
}

func (rc *InstanceOptions) CloneConsumer() *global.ConsumerConfig {
	if rc.Consumer == nil {
		return nil
	}
	return rc.Consumer.Clone()
}

func (rc *InstanceOptions) CloneMetrics() *global.MetricsConfig {
	if rc.Metrics == nil {
		return nil
	}
	return rc.Metrics.Clone()
}

func (rc *InstanceOptions) CloneOtel() *global.OtelConfig {
	if rc.Otel == nil {
		return nil
	}
	return rc.Otel.Clone()
}

func (rc *InstanceOptions) CloneLogger() *global.LoggerConfig {
	if rc.Logger == nil {
		return nil
	}
	return rc.Logger.Clone()
}

func (rc *InstanceOptions) CloneShutdown() *global.ShutdownConfig {
	if rc.Shutdown == nil {
		return nil
	}
	return rc.Shutdown.Clone()
}

func (rc *InstanceOptions) CloneCustom() *global.CustomConfig {
	if rc.Custom == nil {
		return nil
	}
	return rc.Custom.Clone()
}

func (rc *InstanceOptions) CloneProfiles() *global.ProfilesConfig {
	if rc.Profiles == nil {
		return nil
	}
	return rc.Profiles.Clone()
}

func (rc *InstanceOptions) CloneTLSConfig() *global.TLSConfig {
	if rc.TLSConfig == nil {
		return nil
	}
	return rc.TLSConfig.Clone()
}

type InstanceOption func(*InstanceOptions)

func WithOrganization(organization string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Organization = organization
	}
}

func WithName(name string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Name = name
	}
}

func WithModule(module string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Module = module
	}
}

func WithGroup(group string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Group = group
	}
}

func WithVersion(version string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Version = version
	}
}

func WithOwner(owner string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Owner = owner
	}
}

func WithEnvironment(environment string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Environment = environment
	}
}

func WithRemoteMetadata() InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.MetadataType = constant.RemoteMetadataStorageType
	}
}

func WithTag(tag string) InstanceOption {
	return func(opts *InstanceOptions) {
		opts.Application.Tag = tag
	}
}

func WithMetadataServicePort(port int) InstanceOption {
	return func(insOpts *InstanceOptions) {
		insOpts.Application.MetadataServicePort = strconv.Itoa(port)
	}
}

func WithMetadataServiceProtocol(protocol string) InstanceOption {
	return func(insOpts *InstanceOptions) {
		insOpts.Application.MetadataServiceProtocol = protocol
	}
}

// TODO: deal this fuction
// this function I want handle the protocol.Option which
// both server and client can use together.
// like:
//
//	func WithProtocol(opts ...protocol.Option) InstanceOption {
//		proOpts := protocol.NewOptions(opts...)
//
//		log.Warnf("proOpts: %+v", proOpts)
//
//		return func(insOpts *InstanceOptions) {
//			if insOpts.Protocols == nil {
//				insOpts.Protocols = make(map[string]*global.ProtocolConfig)
//			}
//			insOpts.Protocols[proOpts.ID] = proOpts.Protocol
//		}
//	}
//
// but now only work in server side for compat old API.
func WithProtocol(opts ...protocol.ServerOption) InstanceOption {
	proOpts := protocol.NewServerOptions(opts...)

	return func(insOpts *InstanceOptions) {
		if insOpts.Protocols == nil {
			insOpts.Protocols = make(map[string]*global.ProtocolConfig)
		}
		insOpts.Protocols[proOpts.ID] = proOpts.Protocol
	}
}

func WithRegistry(opts ...registry.Option) InstanceOption {
	regOpts := registry.NewOptions(opts...)

	return func(insOpts *InstanceOptions) {
		if insOpts.Registries == nil {
			insOpts.Registries = make(map[string]*global.RegistryConfig)
		}
		insOpts.Registries[regOpts.ID] = regOpts.Registry
	}
}

// WithTracing otel configuration, currently only supports tracing
func WithTracing(opts ...trace.Option) InstanceOption {
	traceOpts := trace.NewOptions(opts...)

	return func(insOpts *InstanceOptions) {
		insOpts.Otel.TracingConfig = traceOpts.Otel.TracingConfig
	}
}

func WithConfigCenter(opts ...config_center.Option) InstanceOption {
	configOpts := config_center.NewOptions(opts...)

	return func(cfg *InstanceOptions) {
		cfg.ConfigCenter = configOpts.Center
	}
}

func WithMetadataReport(opts ...metadata.ReportOption) InstanceOption {
	metadataOpts := metadata.NewReportOptions(opts...)

	return func(cfg *InstanceOptions) {
		cfg.MetadataReport = metadataOpts.MetadataReportConfig
	}
}

func WithMetrics(opts ...metrics.Option) InstanceOption {
	metricOpts := metrics.NewOptions(opts...)

	return func(cfg *InstanceOptions) {
		cfg.Metrics = metricOpts.Metrics
	}
}

func WithLogger(opts ...logger.Option) InstanceOption {
	loggerOpts := logger.NewOptions(opts...)

	return func(cfg *InstanceOptions) {
		cfg.Logger = loggerOpts.Logger
	}
}

func WithShutdown(opts ...graceful_shutdown.Option) InstanceOption {
	sdOpts := graceful_shutdown.NewOptions(opts...)

	return func(insOpts *InstanceOptions) {
		insOpts.Shutdown = sdOpts.Shutdown
	}
}

// todo(DMwangnima): enumerate specific EventDispatcherType
//func WithEventDispatcherType(typ string) InstanceOption {
//	return func(cfg *InstanceOptions) {
//		cfg.EventDispatcherType = typ
//	}
//}

//func WithCacheFile(file string) InstanceOption {
//	return func(cfg *InstanceOptions) {
//		cfg.CacheFile = file
//	}
//}

//func WithCustom(opts ...global.CustomOption) InstanceOption {
//	cusCfg := new(global.CustomConfig)
//	for _, opt := range opts {
//		opt(cusCfg)
//	}
//
//	return func(cfg *InstanceOptions) {
//		cfg.Custom = cusCfg
//	}
//}

//func WithProfiles(opts ...global.ProfilesOption) InstanceOption {
//	proCfg := new(global.ProfilesConfig)
//	for _, opt := range opts {
//		opt(proCfg)
//	}
//
//	return func(cfg *InstanceOptions) {
//		cfg.Profiles = proCfg
//	}
//}

func WithTLS(opts ...tls.Option) InstanceOption {
	tlsOpts := tls.NewOptions(opts...)

	return func(insOpts *InstanceOptions) {
		if insOpts.TLSConfig == nil {
			insOpts.TLSConfig = new(global.TLSConfig)
		}
		insOpts.TLSConfig = tlsOpts.TLSConf
	}
}
