package ffs

import (
	"context"
	"fmt"
	"path"
	"sync"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"github.com/golang/glog"
	"github.com/pingcap/log"
	"github.com/tidbcloud/ffs-csi-driver/pkg/state"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"k8s.io/utils/mount"
)

const (
	DefaultDriverName = "ffs.csi.k8s.io"

	paramUniverse   = "universe"
	paramZone       = "zone"
	paramTenant     = "tenant"
	paramKeepCache  = "keepcache"
	paramAsyncWrite = "asyncwrite"
	paramLogLevel   = "loglevel"
	paramMerge      = "merge"
	paramUID        = "uid"
	paramGID        = "gid"

	argUniverse   = paramUniverse
	argZone       = paramZone
	argTenant     = paramTenant
	argKeepCache  = "keep-cache"
	argAsyncWrite = "async-write"
	argLogLevel   = "log-level"
	argMerge      = paramMerge
	argVolume     = "volume"
	argTarget     = "target"
	argRemove     = "remove"
	argUID        = paramUID
	argGID        = paramGID
)

// DriverOptions defines driver parameters specified in driver deployment
type DriverOptions struct {
	NodeID     string
	DriverName string
	Endpoint   string
	StateDir   string
}

type Driver struct {
	name     string
	nodeID   string
	version  string
	endpoint string

	mutex  sync.Mutex
	state  state.State
	ctx    context.Context
	cancel context.CancelFunc
	wg     sync.WaitGroup

	//ids         *identityServer
	ns    *NodeServer
	vcap  []*csi.VolumeCapability_AccessMode
	cscap []*csi.ControllerServiceCapability
	nscap []*csi.NodeServiceCapability
}

func NewDriver(options *DriverOptions) *Driver {
	log.S().Infof("Driver: %v version: %v", options.DriverName, driverVersion)

	d := &Driver{
		name:     options.DriverName,
		version:  driverVersion,
		nodeID:   options.NodeID,
		endpoint: options.Endpoint,
	}
	d.ctx, d.cancel = context.WithCancel(context.Background())

	d.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
		csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
		csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
		csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
	})

	d.AddNodeServiceCapabilities([]csi.NodeServiceCapability_RPC_Type{
		csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
		csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
		csi.NodeServiceCapability_RPC_UNKNOWN,
		csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP,
		csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
	})

	d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
		csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
		csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
		csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
		csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
	})
	if options.StateDir != "" {
		s, err := state.New(path.Join(options.StateDir, "state.json"))
		if err != nil {
			log.S().Fatalf("failed to restore state err %s", err)
		}
		d.state = s
		d.check(true)
		d.wg.Add(1)
		go d.run()
	}
	return d
}

func (d *Driver) Run(testMode bool) {
	versionMeta, err := GetVersionYAML(d.name)
	if err != nil {
		log.S().Fatalf("%v", err)
	}
	log.S().Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)

	d.ns = NewNodeServer(d, mount.New(""))
	s := NewNonBlockingGRPCServer()
	s.Start(d.endpoint,
		NewIdentityServer(d),
		NewControllerServer(d),
		d.ns,
		testMode)
	s.Wait()
	d.cancel()
	d.wg.Wait()
}
func (n *Driver) AddVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {
	var vca []*csi.VolumeCapability_AccessMode
	for _, c := range vc {
		glog.Infof("Enabling volume access mode: %v", c.String())
		vca = append(vca, &csi.VolumeCapability_AccessMode{Mode: c})
	}
	n.vcap = vca
	return vca
}

func (n *Driver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) {
	var csc []*csi.ControllerServiceCapability
	for _, c := range cl {
		csc = append(csc, NewControllerServiceCapability(c))
	}
	n.cscap = csc
}

func (n *Driver) AddNodeServiceCapabilities(nl []csi.NodeServiceCapability_RPC_Type) {
	var nsc []*csi.NodeServiceCapability
	for _, n := range nl {
		nsc = append(nsc, NewNodeServiceCapability(n))
	}
	n.nscap = nsc
}

func (d *Driver) ValidateControllerServiceRequest(c csi.ControllerServiceCapability_RPC_Type) error {
	if c == csi.ControllerServiceCapability_RPC_UNKNOWN {
		return nil
	}

	for _, cap := range d.cscap {
		if c == cap.GetRpc().GetType() {
			return nil
		}
	}
	return status.Error(codes.InvalidArgument, fmt.Sprintf("%s", c))
}
