package api

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"git.aiterp.net/rpdata/logbot3/internal/config"
	jwt "github.com/dgrijalva/jwt-go"
)

type queryData struct {
	Query     string                 `json:"query"`
	Variables map[string]interface{} `json:"variables"`
}

type queryResponse struct {
	Errors QueryErrorList  `json:"errors"`
	Data   json.RawMessage `json:"data"`
}

// A Client is a client for the rpdata API
type Client struct {
	http      *http.Client
	endpoint  string
	username  string
	keyID     string
	keySecret string
}

func (client *Client) sign(permissions []string) (token string, err error) {
	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"kid":         "bar",
		"user":        client.username,
		"permissions": permissions,
		"exp":         time.Now().Add(time.Second * 30).Unix(),
	})
	jwtToken.Header["kid"] = client.keyID

	return jwtToken.SignedString([]byte(client.keySecret))
}

// Query sends a query or mutation to the server. The returned value is the data element.
func (client *Client) Query(ctx context.Context, query string, variables map[string]interface{}, permissions []string) (data json.RawMessage, err error) {
	// Get token
	token, err := client.sign(permissions)
	if err != nil {
		return
	}

	// Make body
	qdata := queryData{
		Query:     query,
		Variables: variables,
	}
	buffer := bytes.NewBuffer(make([]byte, 0, 256))
	err = json.NewEncoder(buffer).Encode(&qdata)
	if err != nil {
		return
	}

	// Make request
	req, err := http.NewRequest("POST", client.endpoint, buffer)
	if err != nil {
		return
	}
	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	// Run request
	res, err := client.http.Do(req)
	if err != nil {
		return
	}
	defer res.Body.Close()

	if res.StatusCode >= 500 {
		stuff, _ := ioutil.ReadAll(res.Body)
		fmt.Println(string(stuff))
		return nil, errors.New("Internal server error")
	}

	// Parse response
	resData := queryResponse{}
	err = json.NewDecoder(res.Body).Decode(&resData)
	if err != nil {
		return
	}

	if len(resData.Errors) > 0 {
		err = resData.Errors
		return
	}

	return resData.Data, nil
}

// New makes a new client.
func New(endpoint, username, keyID, keySecret string) *Client {
	return &Client{
		http:      &http.Client{Timeout: 60 * time.Second},
		endpoint:  endpoint,
		username:  username,
		keyID:     keyID,
		keySecret: keySecret,
	}
}

// Global gets the global client, from the config file.
func Global() *Client {
	conf := config.Get()
	return &Client{
		http:      &http.Client{Timeout: 60 * time.Second},
		endpoint:  conf.API.Endpoint,
		username:  conf.API.Username,
		keyID:     conf.API.Key.ID,
		keySecret: conf.API.Key.Secret,
	}
}