package accesscontrol

import (
	"fmt"
	"strings"
	"time"

	"xorm.io/xorm"

	"github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)

const AddActionSetMigrationID = "adding action set permissions"

func AddActionSetPermissionsMigrator(mg *migrator.Migrator) {
	mg.AddMigration(AddActionSetMigrationID, &actionSetMigrator{})
}

type actionSetMigrator struct {
	sess     *xorm.Session
	migrator *migrator.Migrator
	migrator.MigrationBase
}

var _ migrator.CodeMigration = new(actionSetMigrator)

func (m *actionSetMigrator) SQL(migrator.Dialect) string {
	return "code migration"
}

func (m *actionSetMigrator) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
	m.sess = sess
	m.migrator = migrator
	return m.addActionSetActions()
}

func (m *actionSetMigrator) addActionSetActions() error {
	var results []accesscontrol.Permission

	// Find action sets and dashboard permissions for managed roles
	// We don't need all dashboard permissions, just enough to help us determine what action set permissions to add
	sql := `
	SELECT permission.role_id, permission.action, permission.scope FROM permission
		LEFT JOIN role ON permission.role_id = role.id
		WHERE permission.action IN ('dashboards:read', 'dashboards:write', 'dashboards.permissions:read', 'dashboards:view', 'dashboards:edit', 'dashboards:admin', 'folders:view', 'folders:edit', 'folders:admin')
		AND role.name LIKE 'managed:%'
`
	if err := m.sess.SQL(sql).Find(&results); err != nil {
		return fmt.Errorf("failed to query permissions: %w", err)
	}

	// group permissions by map[roleID]map[scope]actionSet
	groupedPermissions := make(map[int64]map[string]string)
	hasActionSet := make(map[int64]map[string]bool)
	for _, result := range results {
		// keep track of which dash/folder permission grants already have an action set permission
		if isActionSetAction(result.Action) {
			if _, ok := hasActionSet[result.RoleID]; !ok {
				hasActionSet[result.RoleID] = make(map[string]bool)
			}
			hasActionSet[result.RoleID][result.Scope] = true
			delete(groupedPermissions[result.RoleID], result.Scope)
			continue
		}

		// don't add action set permissions where they already exist
		if _, has := hasActionSet[result.RoleID]; has && hasActionSet[result.RoleID][result.Scope] {
			continue
		}

		if _, ok := groupedPermissions[result.RoleID]; !ok {
			groupedPermissions[result.RoleID] = make(map[string]string)
		}

		// store the most permissive action set permission
		currentActionSet := groupedPermissions[result.RoleID][result.Scope]
		switch result.Action {
		case "dashboards:read":
			if currentActionSet == "" {
				groupedPermissions[result.RoleID][result.Scope] = "view"
			}
		case "dashboards:write":
			if currentActionSet != "admin" {
				groupedPermissions[result.RoleID][result.Scope] = "edit"
			}
		case "dashboards.permissions:read":
			groupedPermissions[result.RoleID][result.Scope] = "admin"
		}
	}

	toAdd := make([]accesscontrol.Permission, 0, len(groupedPermissions))

	now := time.Now()
	for roleID, permissions := range groupedPermissions {
		for scope, action := range permissions {
			// should never be the case, but keeping this check for extra safety
			if _, ok := hasActionSet[roleID][scope]; ok {
				continue
			}

			if strings.HasPrefix(scope, "folders:") {
				action = fmt.Sprintf("folders:%s", action)
			} else {
				action = fmt.Sprintf("dashboards:%s", action)
			}

			kind, attr, identifier := accesscontrol.SplitScope(scope)
			toAdd = append(toAdd, accesscontrol.Permission{
				RoleID:     roleID,
				Scope:      scope,
				Action:     action,
				Kind:       kind,
				Attribute:  attr,
				Identifier: identifier,
				Created:    now,
				Updated:    now,
			})
		}
	}

	if len(toAdd) > 0 {
		err := batch(len(toAdd), batchSize, func(start, end int) error {
			m.migrator.Logger.Debug(fmt.Sprintf("inserting permissions %v", toAdd[start:end]))
			if _, err := m.sess.InsertMulti(toAdd[start:end]); err != nil {
				return fmt.Errorf("failed to add action sets: %w", err)
			}
			return nil
		})
		if err != nil {
			return err
		}
		m.migrator.Logger.Debug("updated managed roles with dash and folder action set permissions")
	}

	return nil
}

func isActionSetAction(action string) bool {
	return action == "dashboards:view" || action == "dashboards:edit" || action == "dashboards:admin" || action == "folders:view" || action == "folders:edit" || action == "folders:admin"
}
