package jumpservercli

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

	"github.com/go-resty/resty/v2"
	"gopkg.in/twindagger/httpsig.v1"
)

type Config struct {
	Scheme        string
	Addr          string
	AccessKey     string
	SecretKey     string
	BaseAssetName string
}

type client struct {
	baseAssetName string
	raw           *raw
}

func NewJumpServerCli(conf Config) (JumpServerCli, error) {
	var err error
	signer, err := httpsig.NewRequestSigner(conf.AccessKey, conf.SecretKey, "hmac-sha256")
	if err != nil {
		return nil, fmt.Errorf("fail to create signer: %w", err)
	}

	httpClient := resty.New().
		SetTransport(http.DefaultTransport).
		SetTimeout(10 * time.Second).
		SetScheme(conf.Scheme).
		SetBaseURL(conf.Addr).
		SetPreRequestHook(func(_ *resty.Client, r *http.Request) error {
			return signMiddleware(*signer, r)
		}).
		OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
			if !r.IsSuccess() {
				return fmt.Errorf("status: %s, body: %s", r.Status(), string(r.Body()))
			}
			return nil
		})

	c := &client{
		baseAssetName: conf.BaseAssetName,
		raw: &raw{
			scheme: conf.Scheme,
			addr:   conf.Addr,
			signer: signer,
			client: httpClient,
		},
	}

	return c, nil
}

// InitUser creates a jumpserver user and a jumpserver account.
func (c *client) InitUser(ctx context.Context, param InitUserParam) (User, Account, error) {
	createdUser := User{}
	createdAccount := Account{}

	// create a jumpserver user

	groupsResp, err := c.raw.ListUserGroups(ctx, map[string]string{
		"name": "Default",
	}, Page{})
	if err != nil {
		return createdUser, createdAccount, fmt.Errorf("query default group failed: %w", err)
	}
	groups := groupsResp.Results
	if len(groups) == 0 {
		return createdUser, createdAccount, fmt.Errorf("default group not found")
	}

	userToCreate := CreateUserReq{
		Name:     param.Username, // use username as name
		Username: param.Username,
		Email:    param.Email,
		Groups: []UserGroup{
			{
				ID: groups[0].ID,
			},
		},
		MFALevel: UserMFALevel{
			Value: UserMFALevelForce,
		},
		PasswordStrategy: UserPasswordStrategy{
			Value: UserPasswordStrategyEmail,
		},
		IsActive: true,
	}
	createdUser, err = c.raw.CreateUser(ctx, userToCreate)
	if err != nil {
		return createdUser, createdAccount, fmt.Errorf("create user failed: %w", err)
	}

	// create a jumpserver account

	assetsResp, err := c.raw.ListAssets(ctx, map[string]string{
		"name": c.baseAssetName,
	}, Page{})
	if err != nil {
		return createdUser, createdAccount, fmt.Errorf("query base asset failed: %w", err)
	}
	assets := assetsResp.Results
	if len(assets) == 0 {
		return createdUser, createdAccount, fmt.Errorf("base asset not found")
	}

	acctToCreate := CreateAccountReq{
		Name:     param.Username,
		Username: param.Username,
		SecretType: AccountSecretType{
			Value: AccountSecretTypeToken,
		},
		Asset: &Asset{
			ID: assets[0].ID,
		},
	}
	createdAccount, err = c.raw.CreateAccount(ctx, acctToCreate)
	if err != nil {
		return createdUser, createdAccount, fmt.Errorf("create account failed: %w", err)
	}

	return createdUser, createdAccount, nil
}

func (c *client) GetUser(ctx context.Context, username string) (*User, error) {
	resp, err := c.raw.ListUsers(ctx, map[string]string{
		"username": username,
	}, Page{})
	if err != nil {
		return nil, fmt.Errorf("query user failed: %w", err)
	}

	users := resp.Results
	if len(users) == 0 {
		return nil, nil
	}

	return &users[0], nil
}

func (c *client) GetAccount(ctx context.Context, username string) (*Account, error) {
	resp, err := c.raw.ListAccounts(ctx, map[string]string{
		"username": username,
	}, Page{})
	if err != nil {
		return nil, fmt.Errorf("query account failed: %w", err)
	}

	accounts := resp.Results
	if len(accounts) == 0 {
		return nil, nil
	}

	return &accounts[0], nil
}

func (c *client) UpdateAccountToken(ctx context.Context, username string, token string) error {
	acct, err := c.GetAccount(ctx, username)
	if err != nil {
		return err
	}
	if acct == nil {
		return fmt.Errorf("account not found")
	}

	req := PatchAccountTokenReq{
		ID:   acct.ID,
		Name: acct.Name,
		SecretType: AccountSecretType{
			Value: AccountSecretTypeToken,
		},
		Secret: token,
	}
	_, err = c.raw.PatchAccount(ctx, req.ID, req)
	if err != nil {
		return fmt.Errorf("update account token failed: %w", err)
	}

	return nil
}

func (c *client) GetBaseAsset(ctx context.Context) (*Asset, error) {
	resp, err := c.raw.ListAssets(ctx, map[string]string{
		"name": c.baseAssetName,
	}, Page{})
	if err != nil {
		return nil, fmt.Errorf("query base asset failed: %w", err)
	}

	assets := resp.Results
	if len(assets) == 0 {
		return nil, nil
	}
	return &assets[0], nil
}

func (c *client) CreateConnectionToken(ctx context.Context, username string) (*ConnectionToken, error) {
	user, err := c.GetUser(ctx, username)
	if err != nil {
		return nil, fmt.Errorf("query user failed: %w", err)
	}
	if user == nil {
		return nil, fmt.Errorf("user not found")
	}

	asset, err := c.GetBaseAsset(ctx)
	if err != nil {
		return nil, fmt.Errorf("query base asset failed: %w", err)
	}

	req := CreateConnectionTokenReq{
		UserID:        user.ID,
		AssetID:       asset.ID,
		Account:       username,
		Protocol:      AssetTypeKubernetes,
		InputUserName: username,
		InputSecret:   "",
		ConnectMethod: ConnectMethodWebCli,
	}
	token, err := c.raw.CreateConnectionToken(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("create connection token failed: %w", err)
	}

	return &token, nil
}
