package api

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"

	"code.justin.tv/tshadwell/jwt"
	"code.justin.tv/video/spectre/auth"
	"code.justin.tv/video/spectre/backend"
	"code.justin.tv/video/spectre/playhead"
	"code.justin.tv/video/spectre/swift"
	"code.justin.tv/video/spectre/util"

	"code.justin.tv/common/golibs/statsd"
	"code.justin.tv/common/meh-restart/restart"

	"github.com/stvp/rollbar"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"github.com/zenazn/goji/web/middleware"
)

type Router struct {
	StatsdClient statsd.Stats
	Backend      backend.Backender
	AuthHandler  auth.Handler
	RequestChan  chan PlayheadRequest
}

type PlayheadRequest struct {
	ChannelID int
	DoneCh    chan util.Nothing
}

var router Router
var Running bool

func NewRequestChan(b backend.Backender) chan PlayheadRequest {
	requestChan := make(chan PlayheadRequest)
	go func() {
		for {
			request := <-requestChan
			ch, _ := b.GetChannel(request.ChannelID)
			if ch != nil {
				_, found := playhead.GetPlayhead(ch.ID)
				if ch.Active && ch.ActivePlaylistID != -1 && !found {
					playhead.InitPlayhead(*ch)
				}
			}
			request.DoneCh <- util.Nothing{}
		}
	}()
	return requestChan
}

func NewRouter(b *backend.Backend, stats statsd.Stats, authHandler auth.JwtHandler) {
	router = Router{
		StatsdClient: stats,
		Backend:      b,
		AuthHandler:  authHandler,
		RequestChan:  NewRequestChan(b),
	}

	goji.Get("/running", restart.HealthCheck(http.HandlerFunc(router.Running)))
	goji.Use(JSONHeaderMiddleware)
	goji.Use(RecoverPanics)
	// goji.Use(ChitinMiddleware)
	goji.Use(StatsdMiddleware)

	api := web.New()
	goji.Handle("/v1/*", api)
	api.Get("/channels/:channel_id", router.ShowChannel)
	api.Put("/channels/:channel_id", router.UpdateChannel)
	api.Get("/channels/:channel_id/playlists/:playlist_id", router.ShowPlaylist)
	api.Post("/channels/:channel_id/playlists", router.CreatePlaylist)
	api.Put("/channels/:channel_id/playlists/:playlist_id", router.UpdatePlaylist)
	api.Delete("/channels/:channel_id/playlists/:playlist_id", router.DestroyPlaylist)

	// Router will populate the web.C env with information about the match, the
	// pattern etc.
	api.Use(goji.DefaultMux.Router)
	// See https://github.com/zenazn/goji/blob/eaa649ab77fd613bb3d44dde6b4e6043a01eee66/web/mux.go#L113
	api.Use(middleware.SubRouter)

	goji.Get("/enabled_vod_ids", router.EnabledVodIDs)

	goji.Get("/hls/:stream_id/thumb/thumb.jpg", router.ShowThumbnail)
	goji.Get("/hls/:stream_id/:format/index-live.m3u8", router.ShowManifest)
	goji.Get("/hls/:stream_id/:format/py-index-live.m3u8", router.ShowManifest)
	goji.Get("/hls/:stream_id/:format/:file", router.ShowChunk)

	goji.Get("/:stream_id/thumb/thumb.jpg", router.ShowThumbnail)
	goji.Get("/:stream_id/:format/index-live.m3u8", router.ShowManifest)
	goji.Get("/:stream_id/:format/py-index-live.m3u8", router.ShowManifest)
	goji.Get("/:stream_id/:format/:file", router.ShowChunk)
}

func IsRunning() bool {
	return Running && playhead.SyncRunning
}

func (T *Router) Running(w http.ResponseWriter, req *http.Request) {
	if IsRunning() {
		json.NewEncoder(w).Encode(map[string]string{"status": "running", "code": "200"})
	} else {
		w.WriteHeader(http.StatusBadRequest)
	}
}

type ErrorResponse struct {
	Error         string
	StatusCode    int
	StatusMessage string
}

func (T *Router) ServeError(w http.ResponseWriter, r *http.Request, err error) {
	var statusCode int
	switch err {
	case sql.ErrNoRows, swift.ErrChunkNotFound, swift.ErrManifestNotFound:
		statusCode = http.StatusNotFound
	case strconv.ErrSyntax, strconv.ErrRange, backend.ErrChannelAlreadyHasPlaylist, backend.ErrInvalidPlaylistJSON,
		ErrPlayheadNotFound, backend.ErrInvalidVersion, ErrChannelIsEnabled, backend.ErrMaxPlaylistSizeReached, backend.ErrPlaylistIsEmpty:
		statusCode = http.StatusBadRequest
	case auth.ErrAuthorizationTokenRequired, auth.ErrBadToken, auth.ErrBadClaim,
		auth.ErrCurrentChannelNotInObjects, jwt.ErrTooShort, jwt.ErrInvalidSignature, jwt.ErrInvalidHeader:
		statusCode = http.StatusForbidden
	default:
		panic(err)
	}

	w.WriteHeader(statusCode)
	jsonErr := json.NewEncoder(w).Encode(&ErrorResponse{
		Error:         err.Error(),
		StatusCode:    statusCode,
		StatusMessage: http.StatusText(statusCode),
	})
	if jsonErr != nil {
		rollbar.RequestErrorWithStack(rollbar.ERR, r, err, rollbar.BuildStack(0))
	}
}

func idToInt(idString string) (int, error) {
	idInt, err := strconv.Atoi(idString)
	if err != nil {
		return 0, sql.ErrNoRows
	}
	return idInt, nil
}
