package connections

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"golang.org/x/net/context"

	"code.justin.tv/common/twitchhttp"
)

const (
	defaultStatSampleRate = 0.1
	defaultTimingXactName = "connections"
	ver                   = "v1"

	// ManageConnectionsCapability is the Cartman capability required in the
	// authorization token for all create and delete Twitter connection requests
	ManageConnectionsCapability = "connections::manage_connections"

	// StateDelim is the string delimiter used to separate parameters in any state
	// parameters passed through providers' auth flows
	StateDelim = ":"
)

var (
	// ErrUnexpectedResponse is returned if the Connections service returns an
	// unhandled error
	ErrUnexpectedResponse = errors.New("unexpected response from connections service")
	// ErrNoConnection is returned if the user has no connections
	ErrNoConnection = errors.New("no connections found")
)

// Client defines the interface for a client of the Connections service
type Client interface {
	AuthTwitter(ctx context.Context, uid, clientID string, opts *twitchhttp.ReqOpts) (string, error)
	GetTwitterUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) (*TwitterUser, error)
	PostTwitterUser(ctx context.Context, uid, token, verifier, nonce string, opts *twitchhttp.ReqOpts) error
	DeleteTwitterUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) error

	AuthSteam(ctx context.Context, uid string, values url.Values, opts *twitchhttp.ReqOpts) (string, error)
	CallbackSteam(ctx context.Context, uid string, values url.Values, opts *twitchhttp.ReqOpts) error
	GetSteamUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) (*SteamUser, error)
	DeleteSteamUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) error

	AuthYoutube(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) (string, error)
	CallbackYoutube(ctx context.Context, uid string, values url.Values, opts *twitchhttp.ReqOpts) error
	GetYoutubeUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) (*YoutubeUser, error)
	DeleteYoutubeUser(ctx context.Context, uid string, opts *twitchhttp.ReqOpts) error
}

type client struct {
	twitchhttp.Client
}

// RedirectPathResp encodes a redirect path for third party authorization.
type RedirectPathResp struct {
	Path string `json:"path"`
}

// ErrorResponse unmarshals the error message from Connections service.
type ErrorResponse struct {
	Error string `json:"error"`
}

func decodeError(res *http.Response) error {
	var errorResp ErrorResponse
	err := json.NewDecoder(res.Body).Decode(&errorResp)
	if err != nil {
		return ErrUnexpectedResponse
	}

	return errors.New(errorResp.Error)
}

// NewClient instantiates a new client for the Connections service
func NewClient(conf twitchhttp.ClientConf) (Client, error) {
	if conf.TimingXactName == "" {
		conf.TimingXactName = defaultTimingXactName
	}
	twitchClient, err := twitchhttp.NewClient(conf)
	return &client{twitchClient}, err
}

func (c *client) do(ctx context.Context, method, path, stat string, opts *twitchhttp.ReqOpts) (*http.Response, error) {
	req, err := c.NewRequest(method, path, nil)
	if err != nil {
		return nil, err
	}

	merged := twitchhttp.MergeReqOpts(opts, twitchhttp.ReqOpts{
		StatName:       fmt.Sprintf("service.connections.%s", stat),
		StatSampleRate: defaultStatSampleRate,
	})

	return c.Do(ctx, req, merged)
}
