/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

// Package diskencryption handles interaction with a node's state disk.
package diskencryption

import (
	"fmt"

	"github.com/edgelesssys/constellation/v2/internal/cryptsetup"
	"github.com/spf13/afero"
)

const (
	stateMapperDevice = "state"
	initialKeyPath    = "/run/cryptsetup-keys.d/state.key"
	keyslot           = 0
)

// DiskEncryption manages the encrypted state mapper device.
type DiskEncryption struct {
	fs     afero.Fs
	device cryptdevice
}

// New creates a new Cryptsetup.
func New() *DiskEncryption {
	return &DiskEncryption{
		fs:     afero.NewOsFs(),
		device: cryptsetup.New(),
	}
}

// Open opens the cryptdevice.
func (c *DiskEncryption) Open() (free func(), err error) {
	return c.device.InitByName(stateMapperDevice)
}

// UUID gets the device's UUID.
// Only works after calling Open().
func (c *DiskEncryption) UUID() (string, error) {
	return c.device.GetUUID()
}

// UpdatePassphrase switches the initial random passphrase of the mapped crypt device to a permanent passphrase.
// Only works after calling Open().
func (c *DiskEncryption) UpdatePassphrase(passphrase string) error {
	initialPassphrase, err := c.getInitialPassphrase()
	if err != nil {
		return err
	}
	if err := c.device.KeyslotChangeByPassphrase(keyslot, keyslot, initialPassphrase, passphrase); err != nil {
		return err
	}

	// Set token as initialized.
	return c.device.SetConstellationStateDiskToken(cryptsetup.SetDiskInitialized)
}

// getInitialPassphrase retrieves the initial passphrase used on first boot.
func (c *DiskEncryption) getInitialPassphrase() (string, error) {
	passphrase, err := afero.ReadFile(c.fs, initialKeyPath)
	if err != nil {
		return "", fmt.Errorf("reading first boot encryption passphrase from disk: %w", err)
	}
	return string(passphrase), nil
}

type cryptdevice interface {
	InitByName(name string) (func(), error)
	GetUUID() (string, error)
	KeyslotChangeByPassphrase(currentKeyslot int, newKeyslot int, currentPassphrase string, newPassphrase string) error
	SetConstellationStateDiskToken(bool) error
}