package statemdl

import (
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// Statistic - for app application
type Statistic struct {
	ServiceName  string        `json:"serviceName"`
	TotalHits    int           `json:"totalHits"`
	MaxTime      time.Duration `json:"maxTime"`
	MinTime      time.Duration `json:"minTime"`
	TotalTime    time.Duration `json:"totalTime"`
	ErrorCount   int           `json:"errorCount"`
	ErrorTime    *time.Time    `json:"errorTime"`
	LastError    string        `json:"lastError"`
	Description  string        `json:"description"`
	IsRestricted bool          `json:"isRestricted"`
	IsRoleBased  bool          `json:"isRoleBased"`
}

type groupResponse struct {
	GroupTime string `json:"name"`
	Hits      int64  `json:"hits"`
}

type clientResponse struct {
	ServicesState map[string]Statistic `json:"servicesState"`
	TotalHits     int64                `json:"totalHits"`
	CacheHits     int64                `json:"cacheHits"`
	CacheMiss     int64                `json:"cacheMiss"`
	StartTime     time.Time            `json:"startTime"`
	GroupReport   []groupResponse      `json:"groupReport"`
}
type cacheStates struct {
	totalHits      int64
	cacheHits      int64
	cacheMiss      int64
	cacheHitsMutex *sync.Mutex
}

var stateCache map[string]Statistic
var stateMutex = &sync.Mutex{}
var cacheStatistic *cacheStates

var groupName []int64
var groupHits []int64
var groupMutex = &sync.Mutex{}

var currentGroup int64
var nextGroup int64
var nextTime = time.Second * 60

// serverStartTime - server start time
var serverStartTime time.Time

func init() {
	cacheStatistic = &cacheStates{
		cacheHitsMutex: &sync.Mutex{},
	}
	serverStartTime = time.Now()
	stateCache = make(map[string]Statistic)
	current := time.Now()
	groupName = append(groupName, current.Unix())
	groupHits = append(groupHits, 0)
	currentGroup = current.Unix()
	nextGroup = current.Add(nextTime).Unix()
}

func updateGlobalHit() {
	cacheStatistic.cacheHitsMutex.Lock()
	updateGroupCache(cacheStatistic.totalHits)
	cacheStatistic.totalHits++
	cacheStatistic.cacheHitsMutex.Unlock()
}

func updateGroupCache(hitCount int64) {
	groupMutex.Lock()
	current := time.Now()
	if current.Unix() < nextGroup {
		for i := 0; i < len(groupName); i++ {
			if groupName[i] == currentGroup {
				groupHits[i]++
			}
		}
	} else {
		groupName = append(groupName, nextGroup)
		groupHits = append(groupHits, 1)
		currentGroup = nextGroup
		nextGroup = current.Add(nextTime).Unix()
	}
	groupMutex.Unlock()

}

// UpdateServiceState - update entry of service in state map
func UpdateServiceState(serviceName string, servingTime time.Duration, serviceError error, isRestricted, isRoleBased bool) {
	stateMutex.Lock()
	serviceState, ok := stateCache[serviceName]
	if !ok {
		serviceState = Statistic{
			ServiceName:  serviceName,
			IsRestricted: isRestricted,
			IsRoleBased:  isRoleBased,
		}
	}
	serviceState.TotalHits++
	if serviceError != nil {
		serviceState.ErrorCount++
		serviceState.LastError = serviceError.Error()
		ct := time.Now()
		serviceState.ErrorTime = &ct
	} else {
		serviceState.TotalTime += servingTime
		if servingTime > serviceState.MaxTime {
			serviceState.MaxTime = servingTime
		}
		if servingTime < serviceState.MinTime || serviceState.MinTime == 0 {
			serviceState.MinTime = servingTime
		}
	}
	stateCache[serviceName] = serviceState
	stateMutex.Unlock()
	updateGlobalHit()
}

// UpdateGlobalServiceCacheState - update only cache hits and miss count for all services
func UpdateGlobalServiceCacheState(cacheHit bool) {
	cacheStatistic.cacheHitsMutex.Lock()
	defer cacheStatistic.cacheHitsMutex.Unlock()
	if cacheHit {
		cacheStatistic.cacheHits++
	} else {
		cacheStatistic.cacheMiss++
	}
}

// StateHandler handler function for sta
func StateHandler(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")
	clientResponseData := &clientResponse{}
	clientResponseData.StartTime = serverStartTime
	cacheStatistic.cacheHitsMutex.Lock()
	clientResponseData.TotalHits = cacheStatistic.totalHits
	clientResponseData.CacheHits = cacheStatistic.cacheHits
	clientResponseData.CacheMiss = cacheStatistic.cacheMiss
	cacheStatistic.cacheHitsMutex.Unlock()
	groupMutex.Lock()
	current := time.Now()
	if current.Unix() > nextGroup {
		tmp := time.Unix(currentGroup, 0)
		for {
			tmp = tmp.Add(nextTime)
			if tmp.Unix() >= current.Unix() {
				break
			}
			groupName = append(groupName, tmp.Unix())
			groupHits = append(groupHits, 0)
			currentGroup = tmp.Unix()
			nextGroup = tmp.Add(nextTime).Unix()
		}
	}
	for i, name := range groupName {
		gr := groupResponse{}
		// gr.GroupTime = int64(time.Unix(name, 0).Second())
		gr.GroupTime = time.Unix(name, 0).String()
		gr.Hits = groupHits[i]
		clientResponseData.GroupReport = append(clientResponseData.GroupReport, gr)
	}
	if len(clientResponseData.GroupReport) > 10 {
		clientResponseData.GroupReport = clientResponseData.GroupReport[len(clientResponseData.GroupReport)-10:]
	}
	groupMutex.Unlock()
	clientResponseData.ServicesState = stateCache
	c.JSON(http.StatusOK, clientResponseData)
}