// Package jax contains a simple Jax client that allows fetching streams from Jax using a query.
package jax

import (
	"bytes"
	"fmt"
	"strings"

	"code.justin.tv/common/twitchhttp"
	"golang.org/x/net/context"
)

const (
	defaultStatSampleRate = 0.1
	defaultTimingXactName = "jax"
)

// Client represents a Jax client
type Client interface {
	// Returns up to one stream matching a query.
	// Query can be modified with options functions.
	GetStream(ctx context.Context, channel string, opts *StreamOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error)

	// Returns up to one stream matching a query, by channel id.
	// Query can be modified with options functions.
	GetStreamByID(ctx context.Context, channelID string, opts *StreamOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error)

	// Returns a list of streams from a list of channel ids.
	// Accepts an array of options to filter out streams.
	GetStreamsByChannelIDs(ctx context.Context, ids []string, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error)

	// Returns a list of streams from a list of channel names.
	// Accepts an array of options to filter out streams.
	GetStreamsByChannels(ctx context.Context, channels []string, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error)

	// Returns all streams matching a query.
	// Query can be modified with options functions.
	GetStreams(ctx context.Context, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error)

	// Returns streams summary
	GetStreamSummary(ctx context.Context, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*StreamSummary, error)
}

type clientImpl struct {
	twitchhttp.Client
}

// NewClient returns an object implementing the Client interface
func NewClient(conf twitchhttp.ClientConf) (Client, error) {
	if conf.TimingXactName == "" {
		conf.TimingXactName = defaultTimingXactName
	}
	twitchClient, err := twitchhttp.NewClient(conf)
	return &clientImpl{twitchClient}, err
}

func (c *clientImpl) GetStream(ctx context.Context, channel string, opts *StreamOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error) {
	req, err := c.NewRequest("GET", fmt.Sprintf("/stream?channel=%s&%s", channel, opts.urlParams().Encode()), nil)
	if err != nil {
		return nil, err
	}

	var streams Streams
	_, err = c.DoJSON(ctx, &streams, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_stream",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &streams, err
}

func (c *clientImpl) GetStreamByID(ctx context.Context, channelID string, opts *StreamOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error) {
	req, err := c.NewRequest("GET", fmt.Sprintf("/stream?id=%s&%s", channelID, opts.urlParams().Encode()), nil)
	if err != nil {
		return nil, err
	}

	var streams Streams
	_, err = c.DoJSON(ctx, &streams, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_stream_by_id",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &streams, err
}

func (c *clientImpl) GetStreamsByChannelIDs(ctx context.Context, channel []string, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error) {
	if len(channel) == 0 {
		return &Streams{
			TotalCount: 0,
			Hits:       []*Stream{},
		}, nil
	}

	channels := "channel_ids=" + strings.Join(channel, ",")

	req, err := c.NewRequest("POST", fmt.Sprintf("/stream/list?%s", opts.urlParams().Encode()), bytes.NewBufferString(channels))
	req.Header.Set("content-type", "application/x-www-form-urlencoded")
	if err != nil {
		return nil, err
	}

	var streams Streams
	_, err = c.DoJSON(ctx, &streams, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_streams_by_ids",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &streams, err
}

func (c *clientImpl) GetStreamsByChannels(ctx context.Context, channels []string, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error) {
	if len(channels) == 0 {
		return &Streams{
			TotalCount: 0,
			Hits:       []*Stream{},
		}, nil
	}

	channelParam := "channels=" + strings.Join(channels, ",")

	req, err := c.NewRequest("POST", fmt.Sprintf("/stream/list?%s", opts.urlParams().Encode()), bytes.NewBufferString(channelParam))
	req.Header.Set("content-type", "application/x-www-form-urlencoded")
	if err != nil {
		return nil, err
	}

	var streams Streams
	_, err = c.DoJSON(ctx, &streams, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_streams_by_logins",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &streams, err
}

func (c *clientImpl) GetStreams(ctx context.Context, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*Streams, error) {
	req, err := c.NewRequest("GET", fmt.Sprintf("/streams?%s", opts.urlParams().Encode()), nil)
	if err != nil {
		return nil, err
	}

	var streams Streams
	_, err = c.DoJSON(ctx, &streams, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_streams",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &streams, err
}

func (c *clientImpl) GetStreamSummary(ctx context.Context, opts *StreamsOptions, reqOpts *twitchhttp.ReqOpts) (*StreamSummary, error) {
	req, err := c.NewRequest("GET", fmt.Sprintf("/stream/summary?%s", opts.urlParams().Encode()), nil)
	if err != nil {
		return nil, err
	}

	var summary StreamSummary
	_, err = c.DoJSON(ctx, &summary, req, twitchhttp.MergeReqOpts(reqOpts, twitchhttp.ReqOpts{
		StatName:       "service.jax.get_summary",
		StatSampleRate: defaultStatSampleRate,
	}))

	return &summary, err
}
