package suite

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"testing"

	"github.com/itchyny/gojq"
	"github.com/stretchr/testify/require"
)

// Payload is an arbitary json object
type Payload map[string]interface{}

// Suite is the test suite which helps you with your tests :)
type Suite struct {
	*http.Client
	Config       Config
	ProjectID    string
	TenantID     string
	t            *testing.T
	doubleCookie string
	currentUser  string
	UserIDs      map[string]string
}

// New creates a new suite and performs the initial login procedure
func New(t *testing.T) *Suite {
	jar, err := cookiejar.New(&cookiejar.Options{})
	require.NoError(t, err)
	s := &Suite{
		Client: &http.Client{
			Jar: jar,
		},
		t:       t,
		UserIDs: map[string]string{},
	}

	s.Config = s.Configuration()
	err = s.Login(s.Config.AdminEmail, s.Config.AdminPassword, s.Config.TenantName)
	require.NoError(t, err)
	return s
}

// Run runs a subtest
func (s *Suite) Run(name string, test func(s *Suite)) {
	s.t.Run(name, func(t *testing.T) {
		s := &Suite{
			Client:       s.Client,
			Config:       s.Config,
			ProjectID:    s.ProjectID,
			TenantID:     s.TenantID,
			UserIDs:      s.UserIDs,
			t:            t,
			doubleCookie: s.doubleCookie,
			currentUser:  s.currentUser,
		}
		test(s)
	})
}

// Skip calls the wrapped t.Skip
func (s *Suite) Skip(args ...interface{}) {
	s.t.Skip(args...)
}

// SetCurrentUser is a helper for switch the user. This the same as
// s.Login(email, password, s.Config.TenantName)
func (s *Suite) SetCurrentUser(email, password string) {
	err := s.Login(email, password, s.Config.TenantName)
	s.Require().NoErrorf(err, "can not login as %s", email)
}

// PlatformURL returns the absolute url pointing to the local platform
func (s *Suite) PlatformURL(path string) string {

	path = strings.TrimPrefix(path, "/")

	if s.Config.TenantSlug != "" && s.Config.IncludeTenantSlug && !strings.HasPrefix(path, "auth") {
		return s.Config.BaseURL + "/" + s.Config.TenantSlug + "/" + path
	}
	return s.Config.BaseURL + "/" + path
}

// ParsePayload tries to json decode the given data
func (s *Suite) ParsePayload(data io.Reader) Payload {
	res := make(Payload)
	buf := &bytes.Buffer{}
	err := json.NewDecoder(io.TeeReader(data, buf)).Decode(&res)
	if err != nil {
		s.t.Errorf("failed to parse payload: %v, body: %v", err, buf.String())
	}
	return res
}

// URLEncode is a helper to escape a value to use as a GET parameter
func (s *Suite) URLEncode(data interface{}) string {
	if str, ok := data.(string); ok {
		return url.QueryEscape(str)
	}
	bs, _ := json.Marshal(data)
	return url.QueryEscape(string(bs))
}

// Jq parse and returns a slice of data from the Payload using a JQ expression
func (s *Suite) Jq(data Payload, expr string, args ...interface{}) (results []interface{}) {
	if data == nil {
		return results
	}
	defer func() {
		if r := recover(); r != nil {
			s.t.Logf("data: %#v", data)
			s.t.Errorf("panic: %v", r)
		}
	}()
	query, err := gojq.Parse(fmt.Sprintf(expr, args...))
	require.NoError(s.t, err)
	iter := query.Run(map[string]interface{}(data))
	for {
		val, ok := iter.Next()
		if !ok {
			break
		}
		results = append(results, val)
	}
	return results
}

// JqVal is the say a Jq but it returns a single values
func (s *Suite) JqVal(data Payload, expr string, args ...interface{}) (result interface{}) {
	results := s.Jq(data, expr, args...)
	s.Require().NotEmpty(results, "data: %+v", data)
	return results[0]
}
