package upgrade

// Security upgrade implementation.
// Creates tokens representing v1 users.

import (
	"context"
	"errors"
	"fmt"
	"sort"

	platform "github.com/influxdata/influxdb/v2"
	platform2 "github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/v1/services/meta"
	"github.com/influxdata/influxql"
	"go.uber.org/zap"
)

// upgradeUsers creates tokens representing v1 users.
func upgradeUsers(
	ctx context.Context,
	v1 *influxDBv1,
	v2 *influxDBv2,
	targetOptions *optionsV2,
	dbBuckets map[string][]platform2.ID,
	log *zap.Logger,
) (int, error) {
	// check if there any 1.x users at all
	v1meta := v1.meta
	if len(v1meta.Users()) == 0 {
		log.Info("There are no users in 1.x, nothing to upgrade")
		return 0, nil
	}

	// get helper instance
	helper := newSecurityUpgradeHelper(log)

	// check if target buckets exists in 2.x
	proceed := helper.checkDbBuckets(v1meta, dbBuckets)
	if !proceed {
		return 0, errors.New("upgrade: there were errors/warnings, please fix them and run the command again")
	}

	// upgrade users
	log.Info("Upgrading 1.x users")
	numUpgraded := 0
	for _, row := range helper.sortUserInfo(v1meta.Users()) {
		username := row.Name
		if row.Admin {
			log.Warn("User is admin and will not be upgraded", zap.String("username", username))
		} else if len(row.Privileges) == 0 {
			log.Warn("User has no privileges and will not be upgraded", zap.String("username", username))
		} else {
			var dbList []string
			for database := range row.Privileges {
				dbList = append(dbList, database)
			}
			var permissions []platform.Permission
			for _, database := range dbList {
				permission := row.Privileges[database]
				for _, id := range dbBuckets[database] {
					switch permission {
					case influxql.ReadPrivilege:
						p, err := platform.NewPermissionAtID(id, platform.ReadAction, platform.BucketsResourceType, targetOptions.orgID)
						if err != nil {
							return numUpgraded, err
						}
						permissions = append(permissions, *p)
					case influxql.WritePrivilege:
						p, err := platform.NewPermissionAtID(id, platform.WriteAction, platform.BucketsResourceType, targetOptions.orgID)
						if err != nil {
							return numUpgraded, err
						}
						permissions = append(permissions, *p)
					case influxql.AllPrivileges:
						p, err := platform.NewPermissionAtID(id, platform.ReadAction, platform.BucketsResourceType, targetOptions.orgID)
						if err != nil {
							return numUpgraded, err
						}
						permissions = append(permissions, *p)
						p, err = platform.NewPermissionAtID(id, platform.WriteAction, platform.BucketsResourceType, targetOptions.orgID)
						if err != nil {
							return numUpgraded, err
						}
						permissions = append(permissions, *p)
					}
				}
			}
			if len(permissions) > 0 {
				auth := &platform.Authorization{
					Description: username + "'s Legacy Token",
					Permissions: permissions,
					Token:       username,
					OrgID:       targetOptions.orgID,
					UserID:      targetOptions.userID,
				}
				err := v2.authSvc.CreateAuthorization(ctx, auth)
				if err != nil {
					log.Error("Failed to create authorization", zap.String("user", username), zap.Error(err))
					continue
				}
				err = v2.authSvc.SetPasswordHash(ctx, auth.ID, row.Hash)
				if err != nil {
					log.Error("Failed to set user's password", zap.String("user", username), zap.Error(err))
					continue
				}
				log.Debug("User upgraded", zap.String("username", username))
				numUpgraded++
			} else {
				log.Warn("User has no privileges and will not be upgraded", zap.String("username", username))
			}
		}
	}

	log.Info("User upgrade complete", zap.Int("upgraded_count", numUpgraded))
	return numUpgraded, nil
}

// securityUpgradeHelper is a helper used by `upgrade` command.
type securityUpgradeHelper struct {
	log *zap.Logger
}

// newSecurityUpgradeHelper returns new security script helper instance for `upgrade` command.
func newSecurityUpgradeHelper(log *zap.Logger) *securityUpgradeHelper {
	helper := &securityUpgradeHelper{
		log: log,
	}

	return helper
}
func (h *securityUpgradeHelper) checkDbBuckets(meta *meta.Client, databases map[string][]platform2.ID) bool {
	ok := true
	for _, row := range meta.Users() {
		for database := range row.Privileges {
			if database == "_internal" {
				continue
			}
			ids := databases[database]
			if len(ids) == 0 {
				h.log.Warn(fmt.Sprintf("No buckets for database [%s] exist in 2.x.", database))
				ok = false
			}
		}
	}

	return ok
}

func (h *securityUpgradeHelper) sortUserInfo(info []meta.UserInfo) []meta.UserInfo {
	sort.Slice(info, func(i, j int) bool {
		return info[i].Name < info[j].Name
	})
	return info
}
