package superset

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/fiskerinc/cloud-services/pkg/httpclient"
	"github.com/fiskerinc/cloud-services/pkg/redis"
	"github.com/pkg/errors"
)

var errUnauthorized = errors.New("superset unauthorized")

type (
	guestTokenRequest struct {
		User      user       `json:"user"`
		Resources []resource `json:"resources"`
		Rls       []rule     `json:"rls"`
	}
	user struct {
		UserName  string `json:"username"`
		FirstName string `json:"first_name"`
		LastName  string `json:"last_name"`
	}
	resource struct {
		ID   string `json:"id"`
		Type string `json:"type"`
	}
	rule struct {
		Clause string `json:"clause"`
	}

	guestTokenResponse struct {
		Token string `json:"token"`
	}
)

func GetGuestToken(r redis.Client, accToken string) (string, error) {
	token, err := getGuestToken(accToken)
	if err == nil {
		return token, nil
	}
	if err != nil && !errors.Is(err, errUnauthorized) {
		return "", err
	}

	// if unauthorized
	accToken, err = loginFunc(r)
	if err != nil {
		return "", err
	}

	return getGuestToken(accToken)
}

func getGuestToken(accToken string) (string, error) {
	req, err := getGuestTokenReq(accToken)
	if err != nil {
		return "", err
	}

	resp, err := httpclient.Client.Do(req)
	if err != nil {
		return "", errors.WithStack(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusUnauthorized {
		return "", errors.WithStack(errUnauthorized)
	}
	if resp.StatusCode != http.StatusOK {
		return "", errors.Errorf("superset guest token answered with status: %s", resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", errors.WithStack(err)
	}

	var structResp guestTokenResponse
	err = json.Unmarshal(body, &structResp)
	if err != nil {
		return "", errors.WithStack(err)
	}

	return structResp.Token, nil
}

func getGuestTokenReq(accToken string) (*http.Request, error) {
	body, err := json.Marshal(guestTokenRequest{
		User: user{
			UserName:  guestUserName,
			FirstName: guestFirstName,
			LastName:  guestLastName,
		},
		Resources: compileResources(accToken),
		Rls:       []rule{},
	})
	if err != nil {
		return nil, errors.WithStack(err)
	}

	req, err := http.NewRequest(http.MethodPost, host+"/security/guest_token", bytes.NewReader(body))
	if err != nil {
		return nil, errors.WithStack(err)
	}
	req.Header.Add("Authorization", "Bearer "+accToken)
	req.Header.Add("Content-Type", "application/json")

	return req, nil
}

func compileResources(accToken string) []resource {
	var res []resource

	dashes, err := GetEmbeddableDashboards(accToken)
	dashIDs := make([]string, 0)
	if err == nil {
		for _, d := range dashes {
			dashIDs = append(dashIDs, d.EmbeddingId)
		}
	}

	for _, id := range dashIDs {
		if id == "" {
			continue
		}
		res = append(res, resource{
			ID:   id,
			Type: "dashboard",
		})
	}

	return res
}
