package blueprint

import (
	"encoding/json"
	"fmt"

	"github.com/osbuild/osbuild-composer/internal/common"
)

type FilesystemCustomization struct {
	Mountpoint string `json:"mountpoint,omitempty" toml:"mountpoint,omitempty"`
	MinSize    uint64 `json:"minsize,omitempty" toml:"minsize,omitempty"`

	// Note: The TOML `size` tag has been deprecated in favor of `minsize`.
	// we check for it in the TOML unmarshaler and use it as `minsize`.
	// However due to the TOML marshaler implementation, we can omit adding
	// a field for this tag and get the benifit of not having to export it.
}

func (fsc *FilesystemCustomization) UnmarshalTOML(data interface{}) error {
	d, _ := data.(map[string]interface{})

	switch d["mountpoint"].(type) {
	case string:
		fsc.Mountpoint = d["mountpoint"].(string)
	default:
		return fmt.Errorf("TOML unmarshal: mountpoint must be string, got %v of type %T", d["mountpoint"], d["mountpoint"])
	}

	var size uint64
	var minsize uint64

	// `size` is an alias for `minsize. We check for the `size` keyword
	// for backwards compatibility. We don't export a `Size` field as
	// we would like to discourage its use.
	switch d["size"].(type) {
	case int64:
		size = uint64(d["size"].(int64))
	case string:
		s, err := common.DataSizeToUint64(d["size"].(string))
		if err != nil {
			return fmt.Errorf("TOML unmarshal: size is not valid filesystem size (%w)", err)
		}
		size = s
	case nil:
		size = 0
	default:
		return fmt.Errorf("TOML unmarshal: size must be integer or string, got %v of type %T", d["size"], d["size"])
	}

	switch d["minsize"].(type) {
	case int64:
		minsize = uint64(d["minsize"].(int64))
	case string:
		s, err := common.DataSizeToUint64(d["minsize"].(string))
		if err != nil {
			return fmt.Errorf("TOML unmarshal: minsize is not valid filesystem size (%w)", err)
		}
		minsize = s
	case nil:
		minsize = 0
	default:
		return fmt.Errorf("TOML unmarshal: minsize must be integer or string, got %v of type %T", d["minsize"], d["minsize"])
	}

	if size == 0 && minsize == 0 {
		return fmt.Errorf("TOML unmarshal: minsize must be greater than 0, got %v", minsize)
	}

	if size > 0 && minsize == 0 {
		fsc.MinSize = size
		return nil
	}

	if size == 0 && minsize > 0 {
		fsc.MinSize = minsize
		return nil
	}

	if size > 0 && minsize > 0 {
		return fmt.Errorf("TOML unmarshal: size and minsize cannot both be set (size is an alias for minsize)")
	}

	return nil
}

func (fsc *FilesystemCustomization) UnmarshalJSON(data []byte) error {
	var v interface{}
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}
	d, _ := v.(map[string]interface{})

	switch d["mountpoint"].(type) {
	case string:
		fsc.Mountpoint = d["mountpoint"].(string)
	default:
		return fmt.Errorf("JSON unmarshal: mountpoint must be string, got %v of type %T", d["mountpoint"], d["mountpoint"])
	}

	// The JSON specification only mentions float64 and Go defaults to it: https://go.dev/blog/json
	switch d["minsize"].(type) {
	case float64:
		// Note that it uses different key than the TOML version
		fsc.MinSize = uint64(d["minsize"].(float64))
	case string:
		size, err := common.DataSizeToUint64(d["minsize"].(string))
		if err != nil {
			return fmt.Errorf("JSON unmarshal: size is not valid filesystem size (%w)", err)
		}
		fsc.MinSize = size
	default:
		return fmt.Errorf("JSON unmarshal: minsize must be float64 number or string, got %v of type %T", d["minsize"], d["minsize"])
	}

	return nil
}
