package ffs

import (
	"context"
	"fmt"
	"strings"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"github.com/pingcap/log"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Ordering of elements in the CSI volume id.
// ID is of the form {universe}/{zone}/{tenant}/{name}.
// Adding a new element should always go at the end
// before totalIDElements
const (
	idUniverse = iota
	idZone
	idTenant
	idName
	totalIDElements // Always last
)

const separator = "#"

var _ = csi.ControllerServer(&ControllerServer{})

type ControllerServer struct {
	Driver *Driver
}

func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
	log.S().Info("create volume")
	name := req.GetName()
	if len(name) == 0 {
		log.S().Error("CreateVolume name must be provided")
		return nil, status.Error(codes.InvalidArgument, "CreateVolume name must be provided")
	}

	if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
		log.S().Infof("invalid create volume req: %v", req)
		return nil, err
	}

	if req.GetVolumeCapabilities() == nil {
		log.S().Error("Volume Capabilities missing in request")
		return nil, status.Error(codes.InvalidArgument, "Volume Capabilities missing in request")
	}

	_ = req.GetCapacityRange().GetRequiredBytes()
	parameters := req.GetParameters()
	if parameters == nil {
		parameters = make(map[string]string)
	}
	log.S().Infof("parameters:%v", parameters)

	// validate parameters (case-insensitive)
	for k, v := range parameters {
		switch strings.ToLower(k) {
		case paramUniverse:
			log.S().Infof("ffs universe %s", v)
		case paramZone:
			log.S().Infof("ffs zone %s", v)
		case paramTenant:
			log.S().Infof("ffs tenant %s", v)
		case paramKeepCache:
			log.S().Infof("ffs keep-cache %s", v)
		case paramAsyncWrite:
			log.S().Infof("ffs async-write %s", v)
		case paramMerge:
			log.S().Infof("ffs merge %s", v)
		case paramLogLevel:
			log.S().Infof("ffs log-level %s", v)
		case paramUID:
			log.S().Infof("ffs uid %s", v)
		case paramGID:
			log.S().Infof("ffs gid %s", v)
		default:
			log.S().Errorf("invalid parameter %q in storage class", k)
			return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid parameter %q in storage class", k))
		}
	}

	volumeId := GetVolumeID(parameters[paramUniverse], parameters[paramZone], parameters[paramTenant], name)

	log.S().Infof("create volume %s %s", name, volumeId)

	return &csi.CreateVolumeResponse{
		Volume: &csi.Volume{
			VolumeId:      volumeId,
			CapacityBytes: 0, // by setting it to zero, Provisioner will use PVC requested size as PV size
			VolumeContext: parameters,
		},
	}, nil
}

func (cs *ControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
	log.S().Info("delete volume")

	volumeId := req.VolumeId

	// Check arguments
	if volumeId == "" {
		log.S().Error("Volume ID missing in request")
		return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
	}

	universe, zone, tenant, name, ok := ParseVolumeID(volumeId)
	if !ok || volumeId == name {
		log.S().Errorf("Invalid volume ID in request %s", volumeId)
		return nil, status.Errorf(codes.InvalidArgument, "Invalid volume ID in request %s", volumeId)
	}
	if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
		log.S().Infof("Invalid delete volume req: %v", req)
		return nil, err
	}
	log.S().Infof("Deleting volume %s", volumeId)

	if err := RemoveFFSVolume(universe, zone, tenant, name); err != nil {
		log.S().Errorf("failed to delete volume req: %v", req)
		return nil, err
	}

	log.S().Infof("volume %s has been deleted.", volumeId)
	return &csi.DeleteVolumeResponse{}, nil
}
func (*ControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
	log.S().Info("controller publish volume")
	return &csi.ControllerPublishVolumeResponse{}, nil
}
func (*ControllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
	log.S().Info("controller unpublish volume")
	return &csi.ControllerUnpublishVolumeResponse{}, nil
}
func (cs *ControllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
	log.S().Info("validate volume capabilities")

	// Check arguments
	if req.GetVolumeId() == "" {
		return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
	}
	if req.GetVolumeCapabilities() == nil {
		return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request")
	}

	volCaps := req.GetVolumeCapabilities()
	if len(volCaps) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Volume capabilities not provided")
	}
	var confirmed *csi.ValidateVolumeCapabilitiesResponse_Confirmed
	if isValidVolumeCapabilities(cs.Driver.vcap, volCaps) {
		confirmed = &csi.ValidateVolumeCapabilitiesResponse_Confirmed{VolumeCapabilities: volCaps}
	}

	return &csi.ValidateVolumeCapabilitiesResponse{
		Confirmed: confirmed,
	}, nil
}
func (*ControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
	log.S().Info("list volumes")
	return nil, status.Errorf(codes.Unimplemented, "method ListVolumes not implemented")
}
func (*ControllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) {
	log.S().Info("get capacity")
	return nil, status.Errorf(codes.Unimplemented, "method GetCapacity not implemented")
}
func (cs *ControllerServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
	log.S().Info("controller get capabilities")
	log.S().Infof("Using default ControllerGetCapabilities")

	return &csi.ControllerGetCapabilitiesResponse{
		Capabilities: cs.Driver.cscap,
	}, nil
}
func (*ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
	log.S().Info("create snapshot")
	return nil, status.Errorf(codes.Unimplemented, "method CreateSnapshot not implemented")
}
func (*ControllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
	log.S().Info("delete snapshot")
	return nil, status.Errorf(codes.Unimplemented, "method DeleteSnapshot not implemented")
}
func (*ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
	log.S().Info("list snapshots")
	return nil, status.Errorf(codes.Unimplemented, "method ListSnapshots not implemented")
}
func (*ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
	log.S().Info("controller expand volume")
	return nil, status.Errorf(codes.Unimplemented, "method ControllerExpandVolume not implemented")
}
func (*ControllerServer) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) {
	log.S().Info("controller get volume")
	return nil, status.Errorf(codes.Unimplemented, "method ControllerGetVolume not implemented")
}

func isValidVolumeCapabilities(driverVolumeCaps []*csi.VolumeCapability_AccessMode, volCaps []*csi.VolumeCapability) bool {
	hasSupport := func(cap *csi.VolumeCapability) bool {
		for _, c := range driverVolumeCaps {
			if c.GetMode() == cap.AccessMode.GetMode() {
				return true
			}
		}
		return false
	}

	foundAll := true
	for _, c := range volCaps {
		if !hasSupport(c) {
			foundAll = false
		}
	}
	return foundAll
}
