package swift

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

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

	"github.com/stvp/rollbar"
)

var (
	swiftURL                 = "http://vod.internal.twitch.tv/"
	s3URL                    = "http://s3-us-west-2.amazonaws.com/twitch-video-vods/"
	ErrInvalidManifestSyntax = errors.New("swift: could not parse manifest")

	// This theoretically could only happen if the deletion cron somehow deleted an enabled spectre vod
	ErrChunkNotFound = errors.New("swift: chunk not found")

	// This theoretically could only happen if a vod is in an invalid state and doesn't have a manifest
	ErrManifestNotFound = errors.New("swift: manifest not found")

	ErrEmptyManifest = errors.New("swift: manifest for this vod is empty")
)

// Manifest contains the full list of chunks in a vod
type Manifest struct {
	Duration time.Duration
	Chunks   []Chunk // should this be chunk pointers instead
	Format   string
}

// Chunk is a chunk of video content
type Chunk struct {
	StartTime time.Duration // time since the start of the playlist that this chunk starts
	Duration  time.Duration // duration of this individual chunk
	Uri       string
	VodID     int
}

// GetChunk fetches a chunk from swift
func GetChunk(vodURI, vodOrigin, format, chunkURI string) ([]byte, error) {
	t0 := time.Now()
	response, err := http.Get(hostpath(vodOrigin) + vodURI + "/" + format + "/" + chunkURI)
	if err != nil {
		rollbar.ErrorWithStack(rollbar.WARN, err, rollbar.BuildStack(0))
		return []byte{}, err
	}
	defer response.Body.Close()
	if response.StatusCode == http.StatusNotFound {
		return []byte{}, ErrChunkNotFound
	}
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		rollbar.ErrorWithStack(rollbar.WARN, err, rollbar.BuildStack(0))
		return []byte{}, err
	}

	go util.Stats.Timing("swift.get_chunk", time.Now().Sub(t0))
	return body, nil
}

// GetManifest fetches a manifest from swift
func GetManifest(vodID int, vodURI, vodOrigin, format, vodManifestFile string) (*Manifest, error) {
	t0 := time.Now()
	url := hostpath(vodOrigin) + vodURI + "/" + format + "/" + vodManifestFile
	response, err := http.Get(url)
	if err != nil {
		return &Manifest{}, err
	}
	defer response.Body.Close()
	if response.StatusCode == http.StatusNotFound {
		return &Manifest{}, ErrManifestNotFound
	}
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return &Manifest{}, err
	}

	go util.Stats.Timing("swift.get_manifest", time.Now().Sub(t0))
	m, err := unmarshalManifest(body, vodID)
	m.Format = format
	return m, err
}

func unmarshalManifest(data []byte, vodID int) (*Manifest, error) {
	m := Manifest{}
	for _, line := range strings.Split(string(data), "\n") {
		if strings.Contains(line, "#EXTINF:") {
			duration, err := time.ParseDuration(strings.TrimSuffix(strings.TrimPrefix(line, "#EXTINF:"), ",") + "s")
			if err != nil {
				return &m, ErrInvalidManifestSyntax
			}

			m.Chunks = append(m.Chunks, Chunk{StartTime: m.Duration, Duration: duration, VodID: vodID})
			m.Duration += duration
		} else if strings.Contains(line, ".ts") {
			m.Chunks[len(m.Chunks)-1].Uri = line
		}
	}
	if len(m.Chunks) == 0 {
		return &m, ErrEmptyManifest
	}
	return &m, nil
}

func hostpath(vodOrigin string) string {
	if vodOrigin == "s3" {
		return s3URL
	} else {
		return swiftURL
	}
}

func (c Chunk) Bytes() []byte {
	return []byte(fmt.Sprintf("#EXTINF:%.3f,\n%v", c.Duration.Seconds(), c.Uri))
}
