// 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 (
	"strconv"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

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

const raid5MinNamespaceNumber = 3

var validChunkSizes = map[uint32]bool{
	4: true, 16: true, 32: true, 64: true, 128: true, 256: true, 512: true,
}

var validRaidLevels = map[uint8]bool{
	0: true, 5: true,
}

type stageVolumeContext struct {
	volumeID   string
	deviceID   string
	devicePath string
	chunkSize  uint32
	raidLevel  uint8
	connect    func() error
	disconnect func() error
}

func (ns *NodeServer) prepareStageVolumeContext(volumeID string, volumeCtx map[string]string) (*stageVolumeContext, error) {
	deviceID := volumeCtx[ctxKeyDeviceID]
	strategy := volumeCtx[ctxKeyAllocStrategy]

	switch strategy {
	case backend.AllocStrategyNormal:
		if deviceID == "" {
			return nil, status.Error(codes.InvalidArgument, "deviceID must be specified for normal allocation strategy")
		}
		stageCtx := &stageVolumeContext{
			volumeID:   volumeID,
			deviceID:   deviceID,
			devicePath: "/dev/disk/by-id/nvme-" + deviceID,
		}
		bindNormalConnector(ns, stageCtx)
		return stageCtx, nil

	case backend.AllocStrategyStriped:
		nsnum, err := parseNsnum(volumeCtx[ctxKeyNsnum])
		if err != nil {
			return nil, err
		}
		raidLevel, err := parseRaidLevel(volumeCtx[ctxKeyRaidLevel])
		if err != nil {
			return nil, err
		}
		if err := validateStripingNsnum(raidLevel, nsnum); err != nil {
			return nil, err
		}
		chunkSize, err := parseChunkSize(volumeCtx[ctxKeyChunkSize])
		if err != nil {
			return nil, err
		}
		stageCtx := &stageVolumeContext{
			volumeID:   volumeID,
			devicePath: "/dev/" + volumeID,
			raidLevel:  raidLevel,
			chunkSize:  chunkSize,
		}
		bindStripingConnector(ns, stageCtx)
		return stageCtx, nil

	case backend.AllocStrategyLinear:
		stageCtx := &stageVolumeContext{
			volumeID:   volumeID,
			devicePath: "/dev/" + volumeID,
		}
		bindLinearConnector(ns, stageCtx)
		return stageCtx, nil

	default:
		return nil, status.Errorf(codes.InvalidArgument, "unsupported allocStrategy %q", strategy)
	}
}

func bindNormalConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
	c := ns.connector
	stageCtx.connect = func() error { return c.Connect(stageCtx.volumeID, ns.hostNqn, stageCtx.deviceID) }
	stageCtx.disconnect = func() error { return c.Disconnect(stageCtx.volumeID, ns.hostNqn) }
}

func bindLinearConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
	c := ns.connector
	stageCtx.connect = func() error { return c.ConnectWithLinearAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath) }
	stageCtx.disconnect = func() error {
		return c.DisconnectWithLinearAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath)
	}
}

func bindStripingConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
	c := ns.connector
	stageCtx.connect = func() error {
		return c.ConnectWithStripingAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath, stageCtx.raidLevel, stageCtx.chunkSize)
	}
	stageCtx.disconnect = func() error {
		return c.DisconnectWithStripingAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath)
	}
}

func validateStripingNsnum(raidLevel uint8, nsnum uint32) error {
	if raidLevel == 5 && nsnum > 0 && nsnum < raid5MinNamespaceNumber {
		return status.Error(codes.InvalidArgument, "raidLevel 5 requires nsnum at least 3")
	}
	return nil
}

func parseRaidLevel(value string) (uint8, error) {
	parsed, err := strconv.ParseUint(value, 10, 8)
	if err != nil || !validRaidLevels[uint8(parsed)] {
		return 0, status.Errorf(codes.InvalidArgument, "unsupported raidLevel %q", value)
	}
	return uint8(parsed), nil
}

func parseChunkSize(value string) (uint32, error) {
	if value == "" {
		return 0, status.Error(codes.InvalidArgument, "chunkSize must be specified for striping volume")
	}
	parsed, err := strconv.ParseUint(value, 10, 32)
	if err != nil {
		return 0, status.Errorf(codes.InvalidArgument, "unsupported chunkSize %q", value)
	}
	chunkSize := uint32(parsed)
	if !validChunkSizes[chunkSize] {
		return 0, status.Errorf(codes.InvalidArgument, "unsupported chunkSize %q", value)
	}
	return chunkSize, nil
}

func parseNsnum(value string) (uint32, error) {
	if value == "" {
		return 0, nil
	}
	parsed, err := strconv.ParseUint(value, 10, 32)
	if err != nil || parsed == 0 {
		return 0, status.Error(codes.InvalidArgument, "nsnum must be positive")
	}
	return uint32(parsed), nil
}