// Copyright Project Harbor Authors
//
// Licensed 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 health

import (
	"context"
	"sort"
	"time"

	"github.com/docker/distribution/health"
)

var (
	timeout  = 60 * time.Second
	registry = map[string]health.Checker{}
	// Ctl is a global health controller
	Ctl = NewController()
)

// NewController returns a health controller instance
func NewController() Controller {
	return &controller{}
}

// Controller defines the health related operations
type Controller interface {
	GetHealth(ctx context.Context) *OverallHealthStatus
}

type controller struct{}

func (c *controller) GetHealth(_ context.Context) *OverallHealthStatus {
	var isHealthy healthy = true
	components := []*ComponentHealthStatus{}
	ch := make(chan *ComponentHealthStatus, len(registry))
	for name, checker := range registry {
		go check(name, checker, timeout, ch)
	}
	for range len(registry) {
		componentStatus := <-ch
		if len(componentStatus.Error) != 0 {
			isHealthy = false
		}
		components = append(components, componentStatus)
	}

	sort.Slice(components, func(i, j int) bool { return components[i].Name < components[j].Name })

	return &OverallHealthStatus{
		Status:     isHealthy.String(),
		Components: components,
	}
}

func check(name string, checker health.Checker,
	timeout time.Duration, c chan *ComponentHealthStatus) {
	statusChan := make(chan *ComponentHealthStatus)
	go func() {
		err := checker.Check()
		var healthy healthy = err == nil
		status := &ComponentHealthStatus{
			Name:   name,
			Status: healthy.String(),
		}
		if !healthy {
			status.Error = err.Error()
		}
		statusChan <- status
	}()

	select {
	case status := <-statusChan:
		c <- status
	case <-time.After(timeout):
		var healthy healthy = false
		c <- &ComponentHealthStatus{
			Name:   name,
			Status: healthy.String(),
			Error:  "failed to check the health status: timeout",
		}
	}
}
