// Copyright F3 Authors
// SPDX-License-Identifier: MIT

package gitlab

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

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/id"
	f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"

	"gitlab.com/gitlab-org/api/client-go"
)

type user struct {
	common
	gitlabUser *gitlab.User
	Password   string
}

var _ f3_tree.ForgeDriverInterface = &user{}

func newUser() generic.NodeDriverInterface {
	return &user{}
}

func (o *user) SetNative(user any) {
	o.gitlabUser = user.(*gitlab.User)
}

func (o *user) GetNativeID() string {
	return util.ToString(o.gitlabUser.ID)
}

func (o *user) NewFormat() f3.Interface {
	node := o.GetNode()
	return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}

func (o *user) ToFormat() f3.Interface {
	if o.gitlabUser == nil {
		return o.NewFormat()
	}
	return &f3.User{
		Common:   f3.NewCommon(util.ToString(o.gitlabUser.ID)),
		UserName: o.gitlabUser.Username,
		Name:     o.gitlabUser.Name,
		Email:    o.gitlabUser.Email,
		IsAdmin:  o.gitlabUser.IsAdmin,
		Password: o.Password,
	}
}

func (o *user) FromFormat(content f3.Interface) {
	user := content.(*f3.User)
	o.gitlabUser = &gitlab.User{
		ID:       int(util.ParseInt(user.GetID())),
		Username: user.UserName,
		Name:     user.Name,
		Email:    user.Email,
		IsAdmin:  user.IsAdmin,
	}
	o.Password = user.Password
}

func (o *user) Get(ctx context.Context) bool {
	node := o.GetNode()
	o.Trace("%s", node.GetID())
	if node.GetID() == id.NilID {
		panic("GetID() == 0")
	}
	user, resp, err := o.getClient().Users.GetUser(node.GetID().Int(), gitlab.GetUsersOptions{})
	if resp.StatusCode == http.StatusNotFound {
		return false
	}
	if err != nil {
		panic(fmt.Errorf("user %v %w", o, err))
	}
	o.gitlabUser = user
	return true
}

func (o *user) Patch(context.Context) {
}

func (o *user) Put(context.Context) id.NodeID {
	skipConfirmation := true

	if o.Password == "" {
		o.Password = util.RandSeq(30)
	}

	u, _, err := o.getClient().Users.CreateUser(&gitlab.CreateUserOptions{
		Username:         &o.gitlabUser.Username,
		Name:             &o.gitlabUser.Name,
		Email:            &o.gitlabUser.Email,
		Password:         &o.Password,
		Admin:            &o.gitlabUser.IsAdmin,
		SkipConfirmation: &skipConfirmation,
	})
	if err != nil {
		panic(fmt.Errorf("%v: %w", o.gitlabUser, err))
	}
	o.gitlabUser = u
	o.Trace("%s %d", o.gitlabUser.Username, o.gitlabUser.ID)
	return id.NewNodeID(u.ID)
}

func (o *user) deleteUser(ctx context.Context) int {
	u := fmt.Sprintf("users/%d", o.gitlabUser.ID)

	type deleteUserOptions struct {
		HardDelete *bool `url:"hard_delete,omitempty" json:"hard_delete,omitempty"`
	}

	hardDelete := true
	req, err := o.getClient().NewRequest(http.MethodDelete, u, &deleteUserOptions{
		HardDelete: &hardDelete,
	}, nil)
	if err != nil {
		panic(fmt.Errorf("NewRequest: %v %v: %w", o.gitlabUser.ID, o.gitlabUser.Username, err))
	}
	resp, err := o.getClient().Do(req, nil)
	if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusNotFound {
		panic(fmt.Errorf("unexpected status code: %v %v: %v %w", o.gitlabUser.ID, o.gitlabUser.Username, resp.StatusCode, err))
	}
	if resp.StatusCode != http.StatusNotFound && err != nil {
		panic(fmt.Errorf("Do: %v %v: %w", o.gitlabUser.ID, o.gitlabUser.Username, err))
	}
	return resp.StatusCode
}

func (o *user) Delete(ctx context.Context) {
	o.Trace("%s %d", o.gitlabUser.Username, o.gitlabUser.ID)
	if o.isSelf(o.gitlabUser.ID) {
		o.Debug("silently ignore a request to delete self")
		return
	}
	statusCode := o.deleteUser(ctx)
	if statusCode != http.StatusNoContent {
		panic(fmt.Errorf("expected user deletion to return 204 but got %d", statusCode))
	}
	loop := 100
	for i := 0; i < loop; i++ {
		if o.deleteUser(ctx) == http.StatusNotFound {
			o.Trace("user deletion complete")
			return
		}
		o.Trace("waiting for asynchronous user deletion (%d/%d)", i, loop)
		time.Sleep(5 * time.Second)
	}
	o.Trace("user still present after %d attempts", loop)
}
