package core

import (
	"time"

	"github.com/TwinProduction/gatus/util"
)

const (
	// MaximumNumberOfResults is the maximum number of results that ServiceStatus.Results can have
	MaximumNumberOfResults = 100

	// MaximumNumberOfEvents is the maximum number of events that ServiceStatus.Events can have
	MaximumNumberOfEvents = 50
)

// ServiceStatus contains the evaluation Results of a Service
type ServiceStatus struct {
	// Name of the service
	Name string `json:"name,omitempty"`

	// Group the service is a part of. Used for grouping multiple services together on the front end.
	Group string `json:"group,omitempty"`

	// Key is the key representing the ServiceStatus
	Key string `json:"key"`

	// Results is the list of service evaluation results
	Results []*Result `json:"results"`

	// Events is a list of events
	//
	// We don't expose this through JSON, because the main dashboard doesn't need to have this data.
	// However, the detailed service page does leverage this by including it to a map that will be
	// marshalled alongside the ServiceStatus.
	Events []*Event `json:"-"`

	// Uptime information on the service's uptime
	//
	// We don't expose this through JSON, because the main dashboard doesn't need to have this data.
	// However, the detailed service page does leverage this by including it to a map that will be
	// marshalled alongside the ServiceStatus.
	Uptime *Uptime `json:"-"`
}

// NewServiceStatus creates a new ServiceStatus
func NewServiceStatus(service *Service) *ServiceStatus {
	return &ServiceStatus{
		Name:    service.Name,
		Group:   service.Group,
		Key:     util.ConvertGroupAndServiceToKey(service.Group, service.Name),
		Results: make([]*Result, 0),
		Events: []*Event{{
			Type:      EventStart,
			Timestamp: time.Now(),
		}},
		Uptime: NewUptime(),
	}
}

// WithResultPagination returns a shallow copy of the ServiceStatus with only the results
// within the range defined by the page and pageSize parameters
func (ss ServiceStatus) WithResultPagination(page, pageSize int) *ServiceStatus {
	shallowCopy := ss
	numberOfResults := len(shallowCopy.Results)
	start := numberOfResults - (page * pageSize)
	end := numberOfResults - ((page - 1) * pageSize)
	if start > numberOfResults {
		start = -1
	} else if start < 0 {
		start = 0
	}
	if end > numberOfResults {
		end = numberOfResults
	}
	if start < 0 || end < 0 {
		shallowCopy.Results = []*Result{}
	} else {
		shallowCopy.Results = shallowCopy.Results[start:end]
	}
	return &shallowCopy
}

// AddResult adds a Result to ServiceStatus.Results and makes sure that there are
// no more than 20 results in the Results slice
func (ss *ServiceStatus) AddResult(result *Result) {
	if len(ss.Results) > 0 {
		// Check if there's any change since the last result
		// OR there's only 1 event, which only happens when there's a start event
		if ss.Results[len(ss.Results)-1].Success != result.Success || len(ss.Events) == 1 {
			event := &Event{Timestamp: result.Timestamp}
			if result.Success {
				event.Type = EventHealthy
			} else {
				event.Type = EventUnhealthy
			}
			ss.Events = append(ss.Events, event)
			if len(ss.Events) > MaximumNumberOfEvents {
				// Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has
				// more than one extra element, we can get rid of all of them at once and thus returning the slice to a
				// length of MaximumNumberOfEvents by using ss.Events[len(ss.Events)-MaximumNumberOfEvents:] instead
				ss.Events = ss.Events[len(ss.Events)-MaximumNumberOfEvents:]
			}
		}
	}
	ss.Results = append(ss.Results, result)
	if len(ss.Results) > MaximumNumberOfResults {
		// Doing ss.Results[1:] would usually be sufficient, but in the case where for some reason, the slice has more
		// than one extra element, we can get rid of all of them at once and thus returning the slice to a length of
		// MaximumNumberOfResults by using ss.Results[len(ss.Results)-MaximumNumberOfResults:] instead
		ss.Results = ss.Results[len(ss.Results)-MaximumNumberOfResults:]
	}
	ss.Uptime.ProcessResult(result)
}
