package jumpservercli

import (
	"context"
	"net/http"
	"strconv"
	"time"

	"gopkg.in/twindagger/httpsig.v1"

	"github.com/go-resty/resty/v2"
)

type Page struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
}

func (p Page) Inject(param map[string]string) {
	param["offset"] = strconv.Itoa(p.Offset)

	if p.Limit == 0 {
		p.Limit = 15
	}
	param["limit"] = strconv.Itoa(p.Limit)
}

type BaseListResponse struct {
	Count    int    `json:"count"`
	Next     string `json:"next"`
	Previous string `json:"previous"`
}

type ListUsersResponse struct {
	BaseListResponse `json:",inline"`

	Results []User `json:"results"`
}

type ListAccountsResponse struct {
	BaseListResponse `json:",inline"`

	Results []Account `json:"results"`
}

type ListUserGroupsResponse struct {
	BaseListResponse `json:",inline"`

	Results []UserGroup `json:"results"`
}

type ListAssetsResponse struct {
	BaseListResponse `json:",inline"`

	Results []Asset `json:"results"`
}

type raw struct {
	scheme string
	addr   string
	client *resty.Client
	signer *httpsig.RequestSigner
}

func (cli *raw) ListAccounts(ctx context.Context, param map[string]string, page Page) (ListAccountsResponse, error) {
	page.Inject(param)

	resp := ListAccountsResponse{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetQueryParams(param).
		SetResult(&resp).
		Get("/api/v1/accounts/accounts/")
	return resp, err
}

func (cli *raw) PatchAccount(ctx context.Context, acctID string, req any) (Account, error) {
	resp := Account{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetPathParam("id", acctID).
		SetBody(req).
		SetResult(&resp).
		Patch("/api/v1/accounts/accounts/{id}/")
	return resp, err
}

func (cli *raw) CreateAccount(ctx context.Context, req CreateAccountReq) (Account, error) {
	resp := Account{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp).
		Post("/api/v1/accounts/accounts/")
	return resp, err
}

func (cli *raw) DeleteAccount(ctx context.Context, acctID string) error {
	_, err := cli.client.R().
		SetContext(ctx).
		SetPathParam("id", acctID).
		Delete("/api/v1/accounts/accounts/{id}/")
	return err
}

func (cli *raw) CreateUser(ctx context.Context, req CreateUserReq) (User, error) {
	resp := User{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp).
		Post("/api/v1/users/users/")
	return resp, err
}

func (cli *raw) ListUsers(ctx context.Context, param map[string]string, page Page) (ListUsersResponse, error) {
	page.Inject(param)

	resp := ListUsersResponse{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetQueryParams(param).
		SetResult(&resp).
		Get("/api/v1/users/users/")
	return resp, err
}

func (cli *raw) GetUser(ctx context.Context, id string) (User, error) {
	resp := User{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetPathParam("id", id).
		SetResult(&resp).
		Get("/api/v1/users/users/{id}/")
	return resp, err
}

func (cli *raw) PatchUser(ctx context.Context, id string, req any) (User, error) {
	resp := User{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetPathParam("id", id).
		SetBody(req).
		SetResult(&resp).
		Patch("/api/v1/users/users/{id}/")
	return resp, err
}

func (cli *raw) ListUserGroups(ctx context.Context, param map[string]string, page Page) (ListUserGroupsResponse, error) {
	page.Inject(param)

	resp := ListUserGroupsResponse{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetQueryParams(param).
		SetResult(&resp).
		Get("/api/v1/users/groups/")
	return resp, err
}

func (cli *raw) ListAssets(ctx context.Context, param map[string]string, page Page) (ListAssetsResponse, error) {
	page.Inject(param)

	resp := ListAssetsResponse{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetQueryParams(param).
		SetResult(&resp).
		Get("/api/v1/assets/assets/")
	return resp, err
}

func (cli *raw) CreateConnectionToken(ctx context.Context, req CreateConnectionTokenReq) (ConnectionToken, error) {
	resp := ConnectionToken{}
	_, err := cli.client.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp).
		Post("/api/v1/authentication/super-connection-token/")
	return resp, err
}

// NOTE: req.URL.Path MUST has the suffix '/' to get the correct sign.
func signMiddleware(signer httpsig.RequestSigner, req *http.Request) error {
	gmtFmt := "Mon, 02 Jan 2006 15:04:05 GMT"
	req.Header.Set("Date", time.Now().Format(gmtFmt))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-JMS-ORG", "00000000-0000-0000-0000-000000000002")

	headers := []string{"(request-target)", "date"}
	return signer.SignRequest(req, headers, nil)
}
