package backend

import (
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"

	"code.justin.tv/video/spectre/usher"

	"github.com/stvp/rollbar"
)

var (
	channelsCache map[int]*Channel
	channelsMutex sync.RWMutex
)

type Channel struct {
	ID                  int
	Enabled             string
	ActivePlaylistID    int `db:"active_playlist_id"`
	ActiveVodIndex      int `db:"active_vod_index"`
	NextVodIndex        int
	Active              bool
	StartTime           time.Time `db:"start_time"`
	XMediaSequenceIndex int       `db:"x_media_sequence_index"`
}

func (c Channel) IsEnabled() bool {
	return c.Enabled != ""
}

func (c Channel) Nonce() string {
	return c.Enabled
}

func (b *Backend) CacheAllChannels() error {
	for {
		channels, err := b.AllChannels()
		if err == nil {
			newCache := make(map[int]*Channel)
			for i, channel := range channels {
				newCache[channel.ID] = &(channels[i])
			}
			channelsMutex.Lock()
			channelsCache = newCache
			channelsMutex.Unlock()
		}
		time.Sleep(time.Duration(5) * time.Second)
	}
}

func (b *Backend) GetChannel(id int) (*Channel, error) {
	if channelsCache == nil {
		return nil, errors.New("Channel cache not yet initialized")
	}
	channelsMutex.RLock()
	channel, ok := channelsCache[id]
	channelsMutex.RUnlock()
	if !ok {
		return nil, errors.New("Channel not in spectre")
	}
	return channel, nil
}

func (b *Backend) GetChannelUncached(id int) (*Channel, error) {
	channel := Channel{}
	err := db.Get(&channel, "SELECT * FROM channels WHERE id = $1", id)
	return &channel, err
}

type ChannelParams struct {
	ID      int  `json:"id"`
	Enabled bool `json:"enabled"`
}

func (b *Backend) UpdateChannel(values ChannelParams) error {
	// Create channel if it doesn't already exist
	_, err := db.Exec(`
		INSERT INTO channels
		SELECT $1, false, -1, 0, false, now()
		WHERE NOT EXISTS (SELECT 1 FROM channels WHERE id = $1);
	`, values.ID)
	if err != nil {
		return err
	}
	var enabled string
	if values.Enabled {
		if err = channelCanBeEnabled(values.ID); err != nil {
			return err
		}
		enabled = strconv.Itoa(int(time.Now().Unix()))
	}

	isLive, err := usher.UsherService{}.IsLive(values.ID)
	if err != nil {
		isLive = true
	}

	active := values.Enabled && !isLive
	activeVodIndex := -1
	if active {
		activeVodIndex = 0
	}

	_, err = db.Exec("UPDATE channels SET enabled = $2, active = $3, active_vod_index = $4, start_time = $5, x_media_sequence_index = 0 WHERE id = $1", values.ID, enabled, active, activeVodIndex, time.Now().UTC())
	return err
}

func (b *Backend) TransitionVod(id int, index int, startTime time.Time, xMediaSequenceIndex int) error {
	fmt.Printf("For channel %v, setting active vod index to %v\n", id, index)
	// TODO: call this from BatchMarkActive?
	_, err := db.Exec("UPDATE channels SET active_vod_index = $2, start_time = $3, x_media_sequence_index = $4 WHERE id = $1", id, index, startTime, xMediaSequenceIndex)
	return err
}

func (b *Backend) AllChannels() ([]Channel, error) {
	channels := []Channel{}
	err := db.Select(&channels, "SELECT * FROM channels")
	return channels, err
}

func (b *Backend) BatchMarkActive(ids []int) {
	// TODO: turn this into 1 sql UPDATE
	for _, id := range ids {
		_, err := db.Exec("UPDATE channels SET active = true, active_vod_index = active_vod_index + 1, start_time = $2, x_media_sequence_index = 0 WHERE id = $1", id, time.Now().UTC())
		if err != nil {
			rollbar.ErrorWithStack(rollbar.ERR, err, rollbar.BuildStack(0))
		}
	}
	if len(ids) > 0 {
		fmt.Printf("Marked channels %v as active\n", ids)
	}
}

func (b *Backend) BatchMarkInactive(ids []int) {
	// TODO: turn this into 1 sql UPDATE http://jmoiron.github.io/sqlx/#inQueries
	for _, id := range ids {
		_, err := db.Exec("UPDATE channels SET active = false WHERE id = $1", id)
		if err != nil {
			rollbar.ErrorWithStack(rollbar.ERR, err, rollbar.BuildStack(0))
		}
	}
	if len(ids) > 0 {
		fmt.Printf("Marked channels %v as inactive\n", ids)
	}
}
