package inventory

import (
	"context"
	"entgo.io/ent/dialect/sql"
	"fmt"
	"github.com/cloudreve/Cloudreve/v4/ent"
	"github.com/cloudreve/Cloudreve/v4/ent/davaccount"
	"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
	"github.com/cloudreve/Cloudreve/v4/pkg/conf"
	"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
	"github.com/samber/lo"
)

type (
	DavAccountClient interface {
		TxOperator
		// List returns a list of dav accounts with the given args.
		List(ctx context.Context, args *ListDavAccountArgs) (*ListDavAccountResult, error)
		// Create creates a new dav account.
		Create(ctx context.Context, params *CreateDavAccountParams) (*ent.DavAccount, error)
		// Update updates a dav account.
		Update(ctx context.Context, id int, params *CreateDavAccountParams) (*ent.DavAccount, error)
		// GetByIDAndUserID returns the dav account with given id and user id.
		GetByIDAndUserID(ctx context.Context, id, userID int) (*ent.DavAccount, error)
		// Delete deletes the dav account.
		Delete(ctx context.Context, id int) error
	}

	ListDavAccountArgs struct {
		*PaginationArgs
		UserID int
	}
	ListDavAccountResult struct {
		*PaginationResults
		Accounts []*ent.DavAccount
	}
	CreateDavAccountParams struct {
		UserID   int
		Name     string
		URI      string
		Password string
		Options  *boolset.BooleanSet
	}
)

func NewDavAccountClient(client *ent.Client, dbType conf.DBType, hasher hashid.Encoder) DavAccountClient {
	return &davAccountClient{
		client:      client,
		hasher:      hasher,
		maxSQlParam: sqlParamLimit(dbType),
	}
}

type davAccountClient struct {
	maxSQlParam int
	client      *ent.Client
	hasher      hashid.Encoder
}

func (c *davAccountClient) SetClient(newClient *ent.Client) TxOperator {
	return &davAccountClient{client: newClient, hasher: c.hasher, maxSQlParam: c.maxSQlParam}
}

func (c *davAccountClient) GetClient() *ent.Client {
	return c.client
}

func (c *davAccountClient) Create(ctx context.Context, params *CreateDavAccountParams) (*ent.DavAccount, error) {
	account := c.client.DavAccount.Create().
		SetOwnerID(params.UserID).
		SetName(params.Name).
		SetURI(params.URI).
		SetPassword(params.Password).
		SetOptions(params.Options)

	return account.Save(ctx)
}

func (c *davAccountClient) GetByIDAndUserID(ctx context.Context, id, userID int) (*ent.DavAccount, error) {
	return c.client.DavAccount.Query().
		Where(davaccount.ID(id), davaccount.OwnerID(userID)).
		First(ctx)
}

func (c *davAccountClient) Update(ctx context.Context, id int, params *CreateDavAccountParams) (*ent.DavAccount, error) {
	account := c.client.DavAccount.UpdateOneID(id).
		SetName(params.Name).
		SetURI(params.URI).
		SetOptions(params.Options)

	return account.Save(ctx)
}

func (c *davAccountClient) Delete(ctx context.Context, id int) error {
	return c.client.DavAccount.DeleteOneID(id).Exec(ctx)
}

func (c *davAccountClient) List(ctx context.Context, args *ListDavAccountArgs) (*ListDavAccountResult, error) {
	query := c.listQuery(args)

	var (
		accounts      []*ent.DavAccount
		err           error
		paginationRes *PaginationResults
	)
	accounts, paginationRes, err = c.cursorPagination(ctx, query, args, 10)

	if err != nil {
		return nil, fmt.Errorf("query failed with paginiation: %w", err)
	}

	return &ListDavAccountResult{
		Accounts:          accounts,
		PaginationResults: paginationRes,
	}, nil
}

func (c *davAccountClient) cursorPagination(ctx context.Context, query *ent.DavAccountQuery, args *ListDavAccountArgs, paramMargin int) ([]*ent.DavAccount, *PaginationResults, error) {
	pageSize := capPageSize(c.maxSQlParam, args.PageSize, paramMargin)
	query.Order(davaccount.ByID(sql.OrderDesc()))

	var (
		pageToken *PageToken
		err       error
	)
	if args.PageToken != "" {
		pageToken, err = pageTokenFromString(args.PageToken, c.hasher, hashid.DavAccountID)
		if err != nil {
			return nil, nil, fmt.Errorf("invalid page token %q: %w", args.PageToken, err)
		}
	}
	queryPaged := getDavAccountCursorQuery(pageToken, query)

	// Use page size + 1 to determine if there are more items to come
	queryPaged.Limit(pageSize + 1)

	logs, err := queryPaged.
		All(ctx)
	if err != nil {
		return nil, nil, err
	}

	// More items to come
	nextTokenStr := ""
	if len(logs) > pageSize {
		lastItem := logs[len(logs)-2]
		nextToken, err := getDavAccountNextPageToken(c.hasher, lastItem)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to generate next page token: %w", err)
		}

		nextTokenStr = nextToken
	}

	return lo.Subset(logs, 0, uint(pageSize)), &PaginationResults{
		PageSize:      pageSize,
		NextPageToken: nextTokenStr,
		IsCursor:      true,
	}, nil
}

func (c *davAccountClient) listQuery(args *ListDavAccountArgs) *ent.DavAccountQuery {
	query := c.client.DavAccount.Query()
	if args.UserID > 0 {
		query.Where(davaccount.OwnerID(args.UserID))
	}

	return query
}

// getDavAccountNextPageToken returns the next page token for the given last dav account.
func getDavAccountNextPageToken(hasher hashid.Encoder, last *ent.DavAccount) (string, error) {
	token := &PageToken{
		ID: last.ID,
	}

	return token.Encode(hasher, hashid.EncodeDavAccountID)
}

func getDavAccountCursorQuery(token *PageToken, query *ent.DavAccountQuery) *ent.DavAccountQuery {
	if token != nil {
		query.Where(davaccount.IDLT(token.ID))
	}

	return query
}
