package backend

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/jmoiron/sqlx/types"
	"github.com/stvp/rollbar"
)

type Playlist struct {
	ID        int            `json:"id"`
	ChannelID int            `json:"channel_id" db:"channel_id"`
	Playlist  types.JsonText `json:"playlist,omitempty"` // TODO: make tests work without omitempty
	Version   int            `json:"version"`
}

var ErrChannelAlreadyHasPlaylist = errors.New("backend: this channel already has a playlist")
var ErrInvalidVersion = errors.New("backend: version is invalid")

func (b *Backend) GetPlaylists(channelID int) ([]Playlist, error) {
	playlists := []Playlist{}
	err := db.Select(&playlists, "SELECT * FROM playlists WHERE channel_id = $1", channelID)
	return playlists, err
}

func (b *Backend) GetPlaylist(channelID int, id int) (*Playlist, error) {
	playlist := Playlist{}
	err := db.Get(&playlist, "SELECT * FROM playlists WHERE channel_id = $1 AND id = $2", channelID, id)

	return &playlist, err
}

type PlaylistParams struct {
	ID        int            `json:"id"`
	ChannelID int            `json:"channel_id"`
	Playlist  types.JsonText `json:"playlist,omitempty"`
}

func (b *Backend) CreatePlaylist(values PlaylistParams) (*Playlist, error) {
	if err := validatePlaylistJson(values.Playlist); err != nil {
		return &Playlist{}, err
	}

	// For v1 launch, each channel can have max 1 playlist
	var numPlaylists int
	err := db.Get(&numPlaylists, "SELECT count(*) FROM playlists WHERE channel_id = $1", values.ChannelID)
	if err != nil {
		return &Playlist{}, err
	}
	if numPlaylists > 0 {
		return &Playlist{}, ErrChannelAlreadyHasPlaylist
	}

	playlist := Playlist{}
	err = db.Get(
		&playlist,
		`INSERT INTO playlists (channel_id, playlist, version) VALUES ($1, $2, 1) RETURNING *`,
		values.ChannelID, values.Playlist,
	)
	if err != nil && strings.Contains(err.Error(), "pq: invalid input syntax for type json") {
		rollbar.Message(rollbar.WARN, fmt.Sprintf("%v made it all the way to the DB", values.Playlist))
		return &Playlist{}, err
	}
	if err != nil {
		return &Playlist{}, err
	}

	// Create channel if it doesn't already exist
	_, err = db.Exec(`
		INSERT INTO channels
		SELECT $1, false, $2, 0, false, now()
		WHERE NOT EXISTS (SELECT 1 FROM channels WHERE id = $1);
	`, playlist.ChannelID, playlist.ID)
	if err != nil {
		return &Playlist{}, err
	}

	// Set channel's active playlist to this playlist's id
	_, err = db.Exec(`
		UPDATE channels
		SET active_playlist_id = $2
		WHERE id = $1;
	`, playlist.ChannelID, playlist.ID)
	if err != nil {
		return &Playlist{}, err
	}

	return &playlist, err
}

func (b *Backend) UpdatePlaylist(values PlaylistParams) (*Playlist, error) {
	if err := validatePlaylistJson(values.Playlist); err != nil {
		return &Playlist{}, err
	}

	_, err := db.Exec("UPDATE playlists SET playlist = $3 WHERE id = $1 AND channel_id = $2", values.ID, values.ChannelID, values.Playlist)

	playlist, err := b.GetPlaylist(values.ChannelID, values.ID)
	if err != nil {
		return &Playlist{}, err
	}

	return playlist, err
}

func (b *Backend) DeletePlaylist(channelID int, id int) error {
	result, err := db.Exec("DELETE FROM playlists WHERE channel_id = $1 AND id = $2", channelID, id)
	if err != nil {
		return err
	}

	if rowsAffected, _ := result.RowsAffected(); rowsAffected == 0 {
		return sql.ErrNoRows
	}

	// If this is the active playlist, set active playlist to -1
	_, err = db.Exec("UPDATE channels SET active_playlist_id = -1 WHERE active_playlist_id = $1", id)

	return err
}

func (p *Playlist) VodIDs() []int {
	var playlist map[string][]int
	err := p.Playlist.Unmarshal(&playlist)
	if err != nil {
		panic(err)
	}
	return playlist["vod_ids"]
}

func (b *Backend) EnabledVodIDs() []int {
	var hs []types.JsonText
	err := db.Select(&hs, "SELECT playlist FROM channels JOIN playlists ON channels.id = playlists.channel_id WHERE channels.enabled != '';")
	if err != nil {
		rollbar.ErrorWithStack(rollbar.WARN, err, rollbar.BuildStack(0))
	}

	// TODO: make this take distinct ids
	// TODO: make this also include vod ids of channels who are enabled but are live broadcasting
	var ids []int
	for _, h := range hs {
		var vods map[string][]int
		h.Unmarshal(&vods)
		ids = append(ids, vods["vod_ids"]...)
	}
	return ids

}
