//go:build windows

package permcheck

import (
	"context"
	"log/slog"

	"github.com/AdguardTeam/golibs/logutil/slogutil"
	"golang.org/x/sys/windows"
)

// needsMigration is the Windows-specific implementation of [NeedsMigration].
func needsMigration(ctx context.Context, l *slog.Logger, workDir, _ string) (ok bool) {
	l = l.With("type", typeDir, "path", workDir)

	dacl, owner, err := getSecurityInfo(workDir)
	if err != nil {
		l.ErrorContext(ctx, "getting security info", slogutil.KeyError, err)

		return true
	}

	if !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid) {
		return true
	}

	err = rangeACEs(dacl, func(
		hdr windows.ACE_HEADER,
		mask windows.ACCESS_MASK,
		sid *windows.SID,
	) (cont bool) {
		switch {
		case hdr.AceType != windows.ACCESS_ALLOWED_ACE_TYPE:
			// Skip non-allowed access control entries.
			l.DebugContext(ctx, "skipping deny access control entry", "sid", sid)
		case !sid.IsWellKnown(windows.WinBuiltinAdministratorsSid):
			// Non-administrator access control entries should not have any
			// access rights.
			ok = mask > 0
		default:
			// Administrators should have full control.
			ok = mask&fullControlMask != fullControlMask
		}

		// Stop ranging if the access control entry is unexpected.
		return !ok
	})
	if err != nil {
		l.ErrorContext(ctx, "checking access control entries", slogutil.KeyError, err)

		return true
	}

	return ok
}

// migrate is the Windows-specific implementation of [Migrate].
//
// It sets the owner to administrators and adds a full control access control
// entry for the account.  It also removes all non-administrator access control
// entries, and keeps deny access control entries.  For any created or modified
// entry it sets the propagation flags to be inherited by child objects.
func migrate(ctx context.Context, logger *slog.Logger, workDir, _, _, _, _ string) {
	l := logger.With("type", typeDir, "path", workDir)

	dacl, owner, err := getSecurityInfo(workDir)
	if err != nil {
		l.ErrorContext(ctx, "getting security info", slogutil.KeyError, err)

		return
	}

	admins, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
	if err != nil {
		l.ErrorContext(ctx, "creating administrators sid", slogutil.KeyError, err)

		return
	}

	// TODO(e.burkov):  Check for duplicates?
	var accessEntries []windows.EXPLICIT_ACCESS
	var setACL bool
	// Iterate over the access control entries in DACL to determine if its
	// migration is needed.
	err = rangeACEs(dacl, func(
		hdr windows.ACE_HEADER,
		mask windows.ACCESS_MASK,
		sid *windows.SID,
	) (cont bool) {
		switch {
		case hdr.AceType != windows.ACCESS_ALLOWED_ACE_TYPE:
			// Add non-allowed access control entries as is, since they specify
			// the access restrictions, which shouldn't be lost.
			l.InfoContext(ctx, "migrating deny access control entry", "sid", sid)
			accessEntries = append(accessEntries, newDenyExplicitAccess(sid, mask))
			setACL = true
		case !sid.IsWellKnown(windows.WinBuiltinAdministratorsSid):
			// Remove non-administrator ACEs, since such accounts should not
			// have any access rights.
			l.InfoContext(ctx, "removing access control entry", "sid", sid)
			setACL = true
		default:
			// Administrators should have full control.  Don't add a new entry
			// here since it will be added later in case there are other
			// required entries.
			l.InfoContext(ctx, "migrating access control entry", "sid", sid, "mask", mask)
			setACL = setACL || mask&fullControlMask != fullControlMask
		}

		return true
	})
	if err != nil {
		l.ErrorContext(ctx, "ranging through access control entries", slogutil.KeyError, err)

		return
	}

	if setACL {
		accessEntries = append(accessEntries, newFullExplicitAccess(admins))
	}

	if !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid) {
		l.InfoContext(ctx, "migrating owner", "sid", owner)
		owner = admins
	} else {
		l.DebugContext(ctx, "owner is already an administrator")
		owner = nil
	}

	err = setSecurityInfo(workDir, owner, accessEntries)
	if err != nil {
		l.ErrorContext(ctx, "setting security info", slogutil.KeyError, err)
	}
}