// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package schematic provides a data model for requested image schematic.
package schematic

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"errors"

	"github.com/siderolabs/gen/xerrors"
	"github.com/siderolabs/talos/pkg/machinery/imager/imageropts"
	"go.yaml.in/yaml/v4"
)

// Schematic represents the requested image customization.
type Schematic struct {
	// Owner is the name (username) of the schematic owner.
	//
	// If owner is set, the schematic is only accessible to the owner and
	// requires authentication to access.
	//
	// Note: this field is only supported in Enterprise edition.
	Owner string `yaml:"owner,omitempty"`
	// Overlay represents the overlay options for image generation.
	Overlay Overlay `yaml:"overlay,omitempty"`
	// Customization represents the Talos image customization.
	Customization Customization `yaml:"customization"`
}

// Validate validates the schematic.
func (s *Schematic) Validate(enterprise bool) error {
	if enterprise {
		return s.validateEnterprise()
	}

	return s.validate()
}

func (s *Schematic) validate() error {
	var errs error

	if s.Owner != "" {
		errs = errors.Join(errs, xerrors.NewTaggedf[InvalidErrorTag]("owner field is only supported in Enterprise edition"))
	}

	return errs
}

func (s *Schematic) validateEnterprise() error {
	return nil
}

// Customization represents the Talos image customization.
//
//nolint:govet // field order is part of the stable schematic serialization (see ID()), so it cannot be reordered for alignment
type Customization struct {
	// EmbeddedMachineConfiguration is the initial embedded machine configuration.
	EmbeddedMachineConfiguration string `yaml:"embeddedMachineConfiguration,omitempty"`
	// Extra kernel arguments to be passed to the kernel.
	ExtraKernelArgs []string `yaml:"extraKernelArgs,omitempty"`
	// Meta provides initial META contents for the image.
	Meta []MetaValue `yaml:"meta,omitempty"`
	// SystemExtensions represents the Talos system extensions to be installed.
	SystemExtensions SystemExtensions `yaml:"systemExtensions,omitempty"`
	// Bootloader specifies the bootloader to be used in the image.
	Bootloader imageropts.BootloaderKind `yaml:"bootloader,omitempty"`
	// SecureBoot represents the secure boot options for the image.
	SecureBoot SecureBootCustomization `yaml:"secureboot,omitempty"`
	// DiskImage represents the disk image options for the image.
	DiskImage DiskImageCustomization `yaml:"diskImage,omitempty"`
}

// MetaValue provides initial META contents for the image.
type MetaValue struct { //nolint:govet
	// Key is the META key.
	Key uint8 `yaml:"key"`
	// Value is the META value.
	Value string `yaml:"value"`
}

// SystemExtensions represents the Talos system extensions to be installed.
type SystemExtensions struct {
	// OfficialExtensions represents the Talos official system extensions to be installed.
	//
	// The image factory will pick up automatically the version compatible with Talos version.
	OfficialExtensions []string `yaml:"officialExtensions,omitempty"`
}

// Overlay represents the overlay options for image generation.
type Overlay struct { //nolint:govet
	Image   string         `yaml:"image"`
	Name    string         `yaml:"name"`
	Options map[string]any `yaml:"options,omitempty"`
}

// SecureBootCustomization represents the secure boot options for the image.
type SecureBootCustomization struct {
	// EnrollKeys controls how systemd-boot enrolls the SecureBoot keys on first boot
	// (loader.conf secure-boot-enroll): one of "off", "manual", "if-safe", "force".
	//
	// Defaults to "if-safe", which auto-enrolls keys only inside a virtual machine.
	// Use "force" for unattended enrollment on bare-metal when the UEFI firmware is
	// in setup mode.
	EnrollKeys string `yaml:"enrollKeys,omitempty"`
	// Include well-known UEFI certificates in the auto-enrollment database.
	IncludeWellKnownCertificates bool `yaml:"includeWellKnownCertificates,omitempty"`
}

// DiskImageCustomization represents the disk image options for the image.
type DiskImageCustomization struct {
	// SectorSize is the size of the disk image sector in bytes.
	//
	// Defaults to 512 if not set.
	SectorSize uint `yaml:"sectorSize,omitempty"`
}

// InvalidErrorTag is a tag for invalid schematic errors.
type InvalidErrorTag struct{}

// RequiresAuthenticationTag is a tag for schematics that require authentication to access.
type RequiresAuthenticationTag struct{}

// ForbiddenTag is a tag for access denied errors (authenticated but not the owner).
type ForbiddenTag struct{}

// Unmarshal the schematic from text representation.
func Unmarshal(data []byte) (*Schematic, error) {
	dec := yaml.NewDecoder(bytes.NewReader(data))
	dec.KnownFields(true)

	var cfg Schematic

	if err := dec.Decode(&cfg); err != nil {
		return nil, xerrors.NewTagged[InvalidErrorTag](err)
	}

	return &cfg, nil
}

// Marshal the schematic to text representation.
//
// Marshal result should be stable if new schematic fields are added.
func (cfg *Schematic) Marshal() ([]byte, error) {
	return yaml.Marshal(cfg)
}

// ID returns the identifier of the schematic.
//
// ID is stable (does not change if the schematic is same).
// ID matches sha256 hash of the canonical representation of the schematic.
func (cfg *Schematic) ID() (string, error) {
	data, err := cfg.Marshal()
	if err != nil {
		return "", err
	}

	binaryID := sha256.Sum256(data)

	return hex.EncodeToString(binaryID[:]), nil
}
