package monitor

import (
	"errors"
	"fmt"

	"code.justin.tv/video/spectre/backend"
	"code.justin.tv/video/spectre/util"
)

// syncActive sets the `active` field based on if a user is currently live broadcasting
func syncActive(channels []*Channel) {
	toMarkActive := make([]int, 0, len(channels))
	toMarkInactive := make([]int, 0, len(channels))

	for _, channel := range channels {
		if usherStateOutOfSync(channel) {
			updateUsherState(channel)
		}

		if spectreStateOutOfSync(channel) {
			toMarkActive, toMarkInactive = updateSpectreState(channel, toMarkActive, toMarkInactive)
		}
	}

	if len(toMarkActive) > 0 {
		db.BatchMarkActive(toMarkActive)
	}
	if len(toMarkInactive) > 0 {
		db.BatchMarkInactive(toMarkInactive)
	}

	numDeltas := len(toMarkActive) + len(toMarkInactive)
	util.Stats.IncrByUnsampled("spectre_monitor_deltas", numDeltas)
}

func usherStateOutOfSync(c *Channel) bool {
	return c.ActiveInUsher != c.IntendedActive()
}

func spectreStateOutOfSync(c *Channel) bool {
	return c.ActiveInUsher != c.Active
}

func updateUsherState(channel *Channel) {
	if channel.IntendedActive() {
		vodIds, err := getVodIds(channel)
		if err != nil {
			disableChannel(channel)
			return
		}
		streamUp(channel, getActiveVodId(channel, vodIds))
	} else {
		streamDown(channel)
	}
}

func updateSpectreState(c *Channel, toMarkActive []int, toMarkInactive []int) ([]int, []int) {
	c.Active = !c.Active
	if c.Active {
		toMarkActive = append(toMarkActive, c.ID)
	} else {
		toMarkInactive = append(toMarkInactive, c.ID)
	}
	return toMarkActive, toMarkInactive
}

func getVodIds(channel *Channel) ([]int, error) {
	playlist, err := db.GetPlaylist(channel.ID, channel.ActivePlaylistID)
	if err != nil {
		fmt.Println("ERROR: could not get playlist for channel: ", channel.ID)
		return nil, err
	}

	vodIds := playlist.VodIDs()
	if len(vodIds) == 0 || len(vodIds) > 50 {
		errorMsg := fmt.Sprintf("Channel playlist is empty or longer than 50; "+
			"channelId=%v, playlistLength=%v\n", channel.ID, len(vodIds))
		fmt.Println(errorMsg)
		return nil, errors.New(errorMsg)
	}

	if channel.ActiveVodIndex < 0 {
		errorMsg := fmt.Sprintf("Channel ActiveVodIndex is < 0; "+
			"channelId=%v, index=%v\n", channel.ID, channel.ActiveVodIndex)
		fmt.Println(errorMsg)
		return nil, errors.New(errorMsg)
	}

	return vodIds, nil
}

func getActiveVodId(channel *Channel, vodIds []int) int {
	index := channel.ActiveVodIndex % len(vodIds)
	return vodIds[index]
}

func streamUp(channel *Channel, vodId int) {
	success, status := usherService.StreamUp(channel.ID, vodId)
	fmt.Printf("Streaming up on channel=%v; success=%v; status=%v\n", channel.ID, success, status)
	if status == 404 {
		disableChannel(channel)
	} else if success {
		channel.ActiveInUsher = true
	}
}

func streamDown(channel *Channel) {
	success, _ := usherService.StreamDown(channel.ID)
	fmt.Printf("Streaming down on channel=%v; success=%v\n", channel.ID, success)
	if success {
		channel.ActiveInUsher = false
	}
}

func disableChannel(channel *Channel) {
	fmt.Println("Disabling channel: ", channel.ID)
	disabled := backend.ChannelParams{ID: channel.ID, Enabled: false}
	db.UpdateChannel(disabled)
}
