package suite

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

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

var (
	platformBaseURL = "http://localhost:9898"
	adminEmail      = "lemon@example.com"
	adminPassword   = "localdev"
	tenantSlug = "lemonade-shop"
	tenantName      = "lemonade shop"
)

// 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
	ProjectID    string
	TenantID     string
	t            *testing.T
	doubleCookie string
	currentUser  string
}

// New creates a new suite and perfoms 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,
	}
	if u := os.Getenv("BASE_URL"); u != "" {
		platformBaseURL = u
	}
	if email := os.Getenv("ADMIN_EMAIL"); email != "" {
		adminEmail = email
	}
	if password := os.Getenv("ADMIN_PASSWORD"); password != "" {
		adminPassword = password
	}
	if tenant := os.Getenv("TENANT_NAME"); tenant != "" {
		tenantName = tenant
	}
	if slug := os.Getenv("TENANT_SLUG"); slug != "" {
		tenantSlug = slug
	}
	err = s.Login(adminEmail, adminPassword, 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,
			ProjectID:    s.ProjectID,
			TenantID:     s.TenantID,
			t:            t,
			doubleCookie: s.doubleCookie,
			currentUser:  adminEmail,
		}
		test(s)
	})
}

func (s *Suite) Skip(args ...interface{}) {
	s.t.Skip(args...)
}

func (s *Suite) SetCurrentUser(email, password string) {
	err := s.Login(email, password, 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 {
	return platformBaseURL + 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
}

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))
}

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.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
}

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]
}
