// Copyright © 2016 Asteris, LLC
//
// 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 resource

import (
	"bytes"
	"errors"
	"fmt"
)

const (
	// StatusHealthy indicates a passing health check
	StatusHealthy HealthStatusCode = iota
	// StatusWarning indicates a change is needed
	StatusWarning
	// StatusError indicates that a module is non-functional
	StatusError
)

const defaultDisplayLevel = StatusWarning

// HealthStatusCode is a status indicator for health level.  It should be one of:
//   StatusHealth
//   StatusWarning
//   StatusError
type HealthStatusCode int

// HealthStatus contains a status level, display threshold, and status message
type HealthStatus struct {
	TaskStatus
	WarningLevel HealthStatusCode
	DisplayLevel *HealthStatusCode
	FailingDeps  map[string]string
}

// HasFailingDeps returns true of FailingDeps is not empty
func (h *HealthStatus) HasFailingDeps() bool {
	return len(h.FailingDeps) > 0
}

// UpgradeWarning will increase the warning level to at least `level`, but will
// not decrease it if it's already higher.
func (h *HealthStatus) UpgradeWarning(level HealthStatusCode) {
	if h.WarningLevel < level {
		h.WarningLevel = level
	}
}

// IsWarning returns true if the warning level is StatusWarning
func (h *HealthStatus) IsWarning() bool {
	return h.WarningLevel == StatusWarning
}

// IsError returns true if the warning level is StatusError
func (h *HealthStatus) IsError() bool {
	return h.WarningLevel == StatusError
}

// ShouldDisplay returns true if the warning level is at least the display level
func (h *HealthStatus) ShouldDisplay() bool {
	var threshold HealthStatusCode
	if h.DisplayLevel == nil {
		threshold = defaultDisplayLevel
	} else {
		threshold = *h.DisplayLevel
	}
	return h.WarningLevel >= threshold
}

// Messages returns health status messages
func (h *HealthStatus) Messages() []string {
	var messages []string
	if statusCode := h.TaskStatus.StatusCode(); statusCode != 0 {
		messages = append(messages, fmt.Sprintf("Check returned %d", statusCode))
	}
	if h.TaskStatus.HasChanges() {
		messages = append(messages, "Check indicates changes are required")
	}
	if depCount := len(h.FailingDeps); depCount > 0 {
		messages = append(messages, fmt.Sprintf("%d failing dependencies", depCount))
	}
	return messages
}

// Changes returns changes from the underlying TaskStatus diffs
func (h *HealthStatus) Changes() map[string]Diff {
	return h.Diffs()
}

// HasChanges returns true if the status indicates that there are changes
func (h *HealthStatus) HasChanges() bool {
	return h.TaskStatus.HasChanges()
}

// Error returns nil on success.  If the health checks or it's dedencies are
// failing an appropriate error is returned.
func (h *HealthStatus) Error() error {
	var msg bytes.Buffer
	var hasError bool
	if h.ShouldDisplay() {
		msg.WriteString("required changes detected")
		hasError = true
	}
	if len(h.FailingDeps) > 0 {
		hasError = true
		if h.ShouldDisplay() {
			msg.WriteString("; ")
		}
		msg.WriteString("required dependencies are failing")
	}
	if hasError {
		return errors.New(msg.String())
	}
	return nil
}
