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

import (
	"context"
	"time"

	"github.com/duke-git/lancet/v2/maputil"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/pkg/errors"

	"github.com/apache/dubbo-admin/pkg/config/app"
	"github.com/apache/dubbo-admin/pkg/core/logger"

	"github.com/apache/dubbo-admin/pkg/config/mode"
)

// Runtime represents initialized application state.
type Runtime interface {
	RuntimeInfo
	RuntimeContext
	ComponentManager
}

type RuntimeInfo interface {
	GetInstanceId() string
	GetClusterId() string
	GetStartTime() time.Time
	GetMode() mode.Mode
}

type RuntimeContext interface {
	Config() app.AdminConfig
	GetComponent(typ ComponentType) (Component, error)
	// AppContext returns a context.Context which tracks the lifetime of the apps, it gets cancelled when the app is starting to shutdown.
	AppContext() context.Context
}

var _ RuntimeInfo = &runtimeInfo{}

type runtimeInfo struct {
	instanceId string
	clusterId  string
	startTime  time.Time
	mode       mode.Mode
}

func (i *runtimeInfo) GetInstanceId() string {
	return i.instanceId
}

func (i *runtimeInfo) GetClusterId() string {
	return i.clusterId
}

func (i *runtimeInfo) GetStartTime() time.Time {
	return i.startTime
}

func (i *runtimeInfo) GetMode() mode.Mode {
	return i.mode
}

var _ RuntimeContext = &runtimeContext{}

type runtimeContext struct {
	cfg        app.AdminConfig
	components map[ComponentType]Component
	appCtx     context.Context
}

func (r *runtimeContext) Config() app.AdminConfig {
	return r.cfg
}

func (r *runtimeContext) GetComponent(typ ComponentType) (Component, error) {
	if comp, exists := r.components[typ]; exists {
		return comp, nil
	}
	return nil, errors.Errorf("component %s not found", typ)
}

func (r *runtimeContext) AppContext() context.Context {
	return r.appCtx
}

var _ Runtime = &runtime{}

type runtime struct {
	runtimeInfo
	runtimeContext
}

func (rt *runtime) Add(components ...Component) {
	for _, c := range components {
		rt.components[c.Type()] = c
	}
}

func (rt *runtime) Start(stop <-chan struct{}) error {
	components := maputil.Values(rt.components)
	slice.SortBy(components, func(a, b Component) bool {
		return a.Order() > b.Order()
	})
	for _, com := range components {
		go func() {
			err := com.Start(rt, stop)
			if err != nil {
				// if a core component failed to start, panic
				if slice.Contain(CoreComponentTypes, com.Type()) {
					panic("core component " + com.Type() + " running failed with error: " + err.Error())
				} else {
					logger.Errorf("component %s running failed with error: %s", com.Type(), err.Error())
				}
			} else {
				logger.Infof("component %s started successfully", com.Type())
			}
		}()
	}
	logger.Info("Admin started successfully")
	select {
	case <-stop:
		return nil
	}
}
