/*
Copyright (c) 2026 Huawei Technologies Co., Ltd.
openFuyao is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
         http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

package driver

import (
	"fmt"
	"strconv"

	csi "github.com/container-storage-interface/spec/lib/go/csi"

	"gitcode.com/openFuyao/ub-ssu-csi/pkg/backend"
)

// StorageClass parameter keys.
const (
	scKeyAllocStrategy = "allocStrategy" // 0 分布式, 1 顺序, 2 普通设备非聚合
	scKeyRaidLevel     = "raidLevel"     // 组逻辑卷 RAID 级别
)

// PVC annotation keys
const (
	pvcKeyLbaFormat = "csi.unifiedbus.com/lbaFormat" // 0=512B, 1=4096B
	pvcKeyNsnum     = "csi.unifiedbus.com/nsnum"     // namespace 数量
	pvcKeyChunksize = "csi.unifiedbus.com/chunksize" // chunk 大小 KB(条带化组卷)
)

const (
	pvcNameKey      = "csi.storage.k8s.io/pvc/name"
	pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace"
)

// volume_context keys.
const (
	ctxKeyNamespaces    = "namespaces" // JSON 序列化的 []backend.Namespace
	ctxKeyVolumeID      = "volumeID"
	ctxKeySizeBytes     = "sizeBytes"
	ctxKeyRaidLevel     = "raidLevel"
	ctxKeyChunkSize     = "chunkSize"
	ctxKeyNsnum         = "nsnum"
	ctxKeyAllocStrategy = "allocStrategy"
	ctxKeyDeviceID      = "deviceID" // 普通卷:namespace 的 DeviceUUID
)

const (
	minSizeBytes uint64 = 1 << 30 // 1GiB,  最小容量

	lbaFormat512  = 0
	lbaFormat4096 = 1

	decimalBase = 10
	bitSize32   = 32
	bitSize8    = 8
)

type volumeParams struct {
	allocStrategy uint8  // 来自 SC, nsnum>1 时必填
	raidLevel     uint8  // 来自 SC, 仅组卷透传
	nsnum         uint32 // 来自 PVC annotation, 缺省 1
	lbaFormat     uint32 // 来自 PVC annotation, 缺省 0
	chunksize     uint32 // 来自 PVC annotation, 仅条带化组卷透传
}

func parseVolumeParams(scParams, pvcAnnotations map[string]string) (volumeParams, error) {
	const defaultStrategy = 2
	p := volumeParams{
		allocStrategy: defaultStrategy, // 缺省普通
		nsnum:         1,
		lbaFormat:     lbaFormat512,
	}

	if v, ok := pvcAnnotations[pvcKeyNsnum]; ok {
		n, err := strconv.ParseUint(v, decimalBase, bitSize32)
		if err != nil || n < 1 {
			return volumeParams{}, fmt.Errorf("invalid nsnum %q: must be positive integer", v)
		}
		p.nsnum = uint32(n)
	}

	if v, ok := pvcAnnotations[pvcKeyLbaFormat]; ok {
		f, err := strconv.ParseUint(v, decimalBase, bitSize32)
		if err != nil || (f != lbaFormat512 && f != lbaFormat4096) {
			return volumeParams{}, fmt.Errorf("invalid lbaFormat %q: must be 0 or 1", v)
		}
		p.lbaFormat = uint32(f)
	}

	if v, ok := pvcAnnotations[pvcKeyChunksize]; ok {
		c, err := strconv.ParseUint(v, decimalBase, bitSize32)
		if err != nil {
			return volumeParams{}, fmt.Errorf("invalid chunksize %q: must be integer", v)
		}
		p.chunksize = uint32(c)
	}

	if v, ok := scParams[scKeyAllocStrategy]; ok {
		s, err := strconv.ParseUint(v, decimalBase, bitSize8)
		if err != nil || s > defaultStrategy {
			return volumeParams{}, fmt.Errorf("invalid allocStrategy %q: must be 0, 1 or 2", v)
		}
		p.allocStrategy = uint8(s)
	} else if p.nsnum > 1 {
		return volumeParams{}, fmt.Errorf("allocStrategy is required in StorageClass parameters when nsnum>1")
	} else {
		// nsnum == 1: use default AllocStrategyNormal (already initialized)
	}

	if v, ok := scParams[scKeyRaidLevel]; ok {
		r, err := strconv.ParseUint(v, decimalBase, bitSize8)
		if err != nil {
			return volumeParams{}, fmt.Errorf("invalid raidLevel %q: must be integer", v)
		}
		p.raidLevel = uint8(r)
	}

	return p, nil
}

func (p volumeParams) toLogicalConfig() backend.LogicalConfig {
	return backend.LogicalConfig{
		NsNum:     p.nsnum,
		LbaFormat: p.lbaFormat,
		Strategy:  p.allocStrategy,
	}
}

func parseRequestedSize(capRange *csi.CapacityRange) (uint64, error) {
	required := uint64(capRange.GetRequiredBytes())
	limit := uint64(capRange.GetLimitBytes())

	if required > 0 && limit > 0 && required > limit {
		return 0, fmt.Errorf("required bytes %d exceeds limit bytes %d", required, limit)
	}

	size := required
	if size == 0 {
		size = limit
	}
	if size == 0 {
		return 0, fmt.Errorf("required bytes and limit bytes are both zero, must specify at least one")
	}
	if size < minSizeBytes {
		return 0, fmt.Errorf("requested size %d bytes is smaller than minimum %d bytes", size, minSizeBytes)
	}
	return size, nil
}

func buildVolumeContext(vol *backend.VolumeInfo) (map[string]string, error) {
	nsJSON, err := backend.MarshalNamespaces(vol.Namespaces)
	if err != nil {
		return nil, fmt.Errorf("marshal namespaces for volume_context: %w", err)
	}

	deviceID := ""
	if len(vol.Namespaces) == 1 {
		deviceID = vol.Namespaces[0].DeviceUUID
	}

	return map[string]string{
		ctxKeyNamespaces:    nsJSON,
		ctxKeyVolumeID:      vol.VolumeID,
		ctxKeySizeBytes:     strconv.FormatUint(vol.SizeBytes, decimalBase),
		ctxKeyRaidLevel:     strconv.FormatUint(uint64(vol.RaidLevel), decimalBase),
		ctxKeyChunkSize:     strconv.FormatUint(uint64(vol.ChunkSize), decimalBase),
		ctxKeyNsnum:         strconv.FormatUint(uint64(len(vol.Namespaces)), decimalBase),
		ctxKeyDeviceID:      deviceID,
		ctxKeyAllocStrategy: strconv.FormatUint(uint64(vol.Strategy), decimalBase),
	}, nil
}