// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.

// +build !oss

package system

import (
	"net/http"

	"github.com/drone/drone/core"
	"github.com/drone/drone/handler/api/render"
	"github.com/drone/drone/logger"
)

type (
	users struct {
		Total int64 `json:"total"`
	}

	repos struct {
		Active int64 `json:"active"`
	}

	builds struct {
		Pending int   `json:"pending"`
		Running int   `json:"running"`
		Total   int64 `json:"total"`
	}

	events struct {
		Subscribers int `json:"subscribers"`
	}

	streams struct {
		Subscribers int `json:"subscribers"`
		Channels    int `json:"channels"`
	}

	platform struct {
		Subscribers int    `json:"subscribers"`
		OS          string `json:"os"`
		Arch        string `json:"arch"`
		Variant     string `json:"variant"`
		Kernel      string `json:"kernel"`
		Pending     int    `json:"pending"`
		Running     int    `json:"running"`
	}

	stats struct {
		Users     users         `json:"users"`
		Repos     repos         `json:"repos"`
		Builds    builds        `json:"builds"`
		Pipelines []*platform   `json:"pipelines"`
		Events    events        `json:"events"`
		Streams   map[int64]int `json:"streams"`
		Watchers  map[int64]int `json:"watchers"`
	}
)

// HandleStats returns an http.HandlerFunc that writes a
// json-encoded list of system stats to the response body.
func HandleStats(
	builds core.BuildStore,
	stages core.StageStore,
	users core.UserStore,
	repos core.RepositoryStore,
	bus core.Pubsub,
	streams core.LogStream,
) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var ctx = r.Context()
		var err error

		//
		// User Stats
		//

		stats := &stats{}
		stats.Users.Total, err = users.Count(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get user count")
			return
		}

		//
		// Repo Stats
		//

		stats.Repos.Active, err = repos.Count(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get repo count")
			return
		}

		//
		// Build Stats
		//

		stats.Builds.Total, err = builds.Count(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get build count")
			return
		}
		buildsPending, err := builds.Pending(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get pending build count")
			return
		}
		buildsRunning, err := builds.Running(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get running build count")
			return
		}
		stats.Builds.Pending = len(buildsPending)
		stats.Builds.Running = len(buildsRunning)

		//
		// Queue Stats
		//

		incomplete, err := stages.ListIncomplete(ctx)
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get pending stage count")
			return
		}
		platforms := newPlatformList()
		aggregatePlatformStats(platforms, incomplete)
		stats.Pipelines = platforms

		//
		// Event Stats
		//

		stats.Events.Subscribers, err = bus.Subscribers()
		if err != nil {
			render.InternalError(w, err)
			logger.FromRequest(r).WithError(err).
				Warnln("stats: cannot get number of subscribers")
			return
		}

		//
		// Stream Stats
		//

		stats.Streams = streams.Info(ctx).Streams

		render.JSON(w, stats, 200)
	}
}

// platform statistics are returned in a fixed array. these
// are pointers to the platform index in the array.
const (
	linuxArm6 int = iota
	linuxArm7
	linuxArm8
	linuxArm9
	linuxAmd64
	windows1709
	windows1803
	windows1809
)

// helper function returns a list of all platforms
// and variants currently supported by core.
func newPlatformList() []*platform {
	platforms := [8]*platform{}
	platforms[linuxArm6] = &platform{OS: "linux", Arch: "arm", Variant: "v6"}
	platforms[linuxArm7] = &platform{OS: "linux", Arch: "arm", Variant: "v7"}
	platforms[linuxArm8] = &platform{OS: "linux", Arch: "arm64", Variant: "v8"}
	platforms[linuxArm9] = &platform{OS: "linux", Arch: "arm", Variant: "v9"}
	platforms[linuxAmd64] = &platform{OS: "linux", Arch: "amd64"}
	platforms[windows1709] = &platform{OS: "windows", Arch: "arm64", Kernel: "1709"}
	platforms[windows1803] = &platform{OS: "windows", Arch: "arm64", Kernel: "1803"}
	platforms[windows1809] = &platform{OS: "windows", Arch: "arm64", Kernel: "1809"}
	return platforms[:]
}

// helper function counts the number of running and
// pending stages by os, architecture, and variant.
func aggregatePlatformStats(platforms []*platform, stages []*core.Stage) {
	for _, stage := range stages {
		var index int
		switch {
		case stage.OS == "windows" && stage.Kernel == "1709":
			index = windows1709
		case stage.OS == "windows" && stage.Kernel == "1803":
			index = windows1803
		case stage.OS == "windows" && stage.Kernel == "1809":
			index = windows1809
		case stage.OS == "windows":
			// default to 1803 when no variant specified
			index = windows1809
		case stage.Arch == "arm" && stage.Variant == "v6":
			index = linuxArm6
		case stage.Arch == "arm" && stage.Variant == "v7":
			index = linuxArm7
		case stage.Arch == "arm" && stage.Variant == "v9":
			index = linuxArm9
		case stage.Arch == "arm":
			// default to arm7 when no variant specified
			index = linuxArm7
		case stage.Arch == "arm64" && stage.Variant == "v8":
			index = linuxArm8
		case stage.Arch == "arm64":
			// default to arm8 when arm64
			index = linuxArm8
		default:
			index = linuxAmd64
			continue
		}

		switch stage.Status {
		case core.StatusPending:
			platforms[index].Pending++
		case core.StatusRunning:
			platforms[index].Running++
		}
	}
}
