package main

import (
	"context"
	"errors"
	"strconv"

	ncproxygrpcv0 "github.com/Microsoft/hcsshim/pkg/ncproxy/ncproxygrpc/v0"
	ncproxygrpc "github.com/Microsoft/hcsshim/pkg/ncproxy/ncproxygrpc/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

var (
	errUnsupportedNetworkType  = errors.New("unsupported network type")
	errUnsupportedEndpointType = errors.New("unsupported endpoint type")
)

type v0ServiceWrapper struct {
	s *grpcService
}

func newV0ServiceWrapper(s *grpcService) *v0ServiceWrapper {
	return &v0ServiceWrapper{s}
}

var _ ncproxygrpcv0.NetworkConfigProxyServer = &v0ServiceWrapper{}

func (w *v0ServiceWrapper) AddNIC(ctx context.Context, req *ncproxygrpcv0.AddNICRequest) (_ *ncproxygrpcv0.AddNICResponse, err error) {
	v1Req := &ncproxygrpc.AddNICRequest{
		ContainerID:  req.ContainerID,
		NicID:        req.NicID,
		EndpointName: req.EndpointName,
	}
	_, err = w.s.AddNIC(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.AddNICResponse{}, nil
}

func (w *v0ServiceWrapper) ModifyNIC(ctx context.Context, req *ncproxygrpcv0.ModifyNICRequest) (_ *ncproxygrpcv0.ModifyNICResponse, err error) {
	v1Req := &ncproxygrpc.ModifyNICRequest{
		ContainerID:  req.ContainerID,
		NicID:        req.NicID,
		EndpointName: req.EndpointName,
	}
	if req.IovPolicySettings != nil {
		v1Req.EndpointSettings = &ncproxygrpc.EndpointSettings{
			Settings: &ncproxygrpc.EndpointSettings_HcnEndpoint{
				HcnEndpoint: &ncproxygrpc.HcnEndpointSettings{
					Policies: &ncproxygrpc.HcnEndpointPolicies{
						IovPolicySettings: &ncproxygrpc.IovEndpointPolicySetting{
							IovOffloadWeight:    req.IovPolicySettings.IovOffloadWeight,
							QueuePairsRequested: req.IovPolicySettings.QueuePairsRequested,
							InterruptModeration: req.IovPolicySettings.InterruptModeration,
						},
					},
				},
			},
		}
	}
	_, err = w.s.ModifyNIC(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.ModifyNICResponse{}, nil
}

func (w *v0ServiceWrapper) DeleteNIC(ctx context.Context, req *ncproxygrpcv0.DeleteNICRequest) (_ *ncproxygrpcv0.DeleteNICResponse, err error) {
	v1Req := &ncproxygrpc.DeleteNICRequest{
		ContainerID:  req.ContainerID,
		NicID:        req.NicID,
		EndpointName: req.EndpointName,
	}
	_, err = w.s.DeleteNIC(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.DeleteNICResponse{}, nil
}

func (w *v0ServiceWrapper) CreateNetwork(ctx context.Context, req *ncproxygrpcv0.CreateNetworkRequest) (_ *ncproxygrpcv0.CreateNetworkResponse, err error) {
	v1Req := &ncproxygrpc.CreateNetworkRequest{
		Network: &ncproxygrpc.Network{
			Settings: &ncproxygrpc.Network_HcnNetwork{
				HcnNetwork: &ncproxygrpc.HostComputeNetworkSettings{
					Name:                  req.Name,
					Mode:                  ncproxygrpc.HostComputeNetworkSettings_NetworkMode(req.Mode),
					SwitchName:            req.SwitchName,
					IpamType:              ncproxygrpc.HostComputeNetworkSettings_IpamType(req.IpamType),
					SubnetIpaddressPrefix: req.SubnetIpaddressPrefix,
					DefaultGateway:        req.DefaultGateway,
				},
			},
		},
	}
	resp, err := w.s.CreateNetwork(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.CreateNetworkResponse{
		ID: resp.ID,
	}, nil
}

func (w *v0ServiceWrapper) CreateEndpoint(ctx context.Context, req *ncproxygrpcv0.CreateEndpointRequest) (_ *ncproxygrpcv0.CreateEndpointResponse, err error) {
	var v1DnsSettings *ncproxygrpc.DnsSetting
	if req.DnsSetting != nil {
		v1DnsSettings = &ncproxygrpc.DnsSetting{
			ServerIpAddrs: req.DnsSetting.ServerIpAddrs,
			Domain:        req.DnsSetting.Domain,
			Search:        req.DnsSetting.Search,
		}
	}
	var v1PortnamePolicySetting *ncproxygrpc.PortNameEndpointPolicySetting
	if req.PortnamePolicySetting != nil {
		v1PortnamePolicySetting = &ncproxygrpc.PortNameEndpointPolicySetting{
			PortName: req.PortnamePolicySetting.PortName,
		}
	}

	var v1IovPolicySettings *ncproxygrpc.IovEndpointPolicySetting
	if req.IovPolicySettings != nil {
		v1IovPolicySettings = &ncproxygrpc.IovEndpointPolicySetting{
			IovOffloadWeight:    req.IovPolicySettings.IovOffloadWeight,
			QueuePairsRequested: req.IovPolicySettings.QueuePairsRequested,
			InterruptModeration: req.IovPolicySettings.InterruptModeration,
		}
	}
	prefixLen, err := strconv.ParseUint(req.IpaddressPrefixlength, 10, 32)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "received invalid ip address prefix length %+v: %v", req, err)
	}
	v1Req := &ncproxygrpc.CreateEndpointRequest{
		EndpointSettings: &ncproxygrpc.EndpointSettings{
			Settings: &ncproxygrpc.EndpointSettings_HcnEndpoint{
				HcnEndpoint: &ncproxygrpc.HcnEndpointSettings{
					Name:                  req.Name,
					Macaddress:            req.Macaddress,
					Ipaddress:             req.Ipaddress,
					IpaddressPrefixlength: uint32(prefixLen),
					NetworkName:           req.NetworkName,
					DnsSetting:            v1DnsSettings,
					Policies: &ncproxygrpc.HcnEndpointPolicies{
						PortnamePolicySetting: v1PortnamePolicySetting,
						IovPolicySettings:     v1IovPolicySettings,
					},
				},
			},
		},
	}
	resp, err := w.s.CreateEndpoint(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.CreateEndpointResponse{
		ID: resp.ID,
	}, nil
}

func (w *v0ServiceWrapper) AddEndpoint(ctx context.Context, req *ncproxygrpcv0.AddEndpointRequest) (_ *ncproxygrpcv0.AddEndpointResponse, err error) {
	v1Req := &ncproxygrpc.AddEndpointRequest{
		Name:        req.Name,
		NamespaceID: req.NamespaceID,
	}
	_, err = w.s.AddEndpoint(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.AddEndpointResponse{}, nil
}

func (w *v0ServiceWrapper) DeleteEndpoint(ctx context.Context, req *ncproxygrpcv0.DeleteEndpointRequest) (_ *ncproxygrpcv0.DeleteEndpointResponse, err error) {
	v1Req := &ncproxygrpc.DeleteEndpointRequest{
		Name: req.Name,
	}
	_, err = w.s.DeleteEndpoint(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.DeleteEndpointResponse{}, nil
}

func (w *v0ServiceWrapper) DeleteNetwork(ctx context.Context, req *ncproxygrpcv0.DeleteNetworkRequest) (_ *ncproxygrpcv0.DeleteNetworkResponse, err error) {
	v1Req := &ncproxygrpc.DeleteNetworkRequest{
		Name: req.Name,
	}
	_, err = w.s.DeleteNetwork(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	return &ncproxygrpcv0.DeleteNetworkResponse{}, nil
}

func (w *v0ServiceWrapper) GetEndpoint(ctx context.Context, req *ncproxygrpcv0.GetEndpointRequest) (_ *ncproxygrpcv0.GetEndpointResponse, err error) {
	v1Req := &ncproxygrpc.GetEndpointRequest{
		Name: req.Name,
	}
	resp, err := w.s.GetEndpoint(ctx, v1Req)
	if err != nil {
		return nil, err
	}
	v0Resp, err := v1EndpointToV0EndpointResp(resp)
	if err != nil {
		if err == errUnsupportedEndpointType {
			return nil, status.Errorf(codes.NotFound, "no endpoint with name `%s` found", req.Name)
		}
		return nil, err
	}

	return v0Resp, nil
}

func v1EndpointToV0EndpointResp(v1EndpointResp *ncproxygrpc.GetEndpointResponse) (_ *ncproxygrpcv0.GetEndpointResponse, err error) {
	if v1EndpointResp.Endpoint != nil {
		if v1EndpointResp.Endpoint.GetHcnEndpoint() != nil {
			v0Resp := &ncproxygrpcv0.GetEndpointResponse{
				ID:        v1EndpointResp.ID,
				Namespace: v1EndpointResp.Namespace,
			}
			v1EndpointSettings := v1EndpointResp.Endpoint.GetHcnEndpoint()
			v0Resp.Name = v1EndpointSettings.Name
			v0Resp.Network = v1EndpointSettings.NetworkName

			if v1EndpointSettings.DnsSetting != nil {
				v0Resp.DnsSetting = &ncproxygrpcv0.DnsSetting{
					ServerIpAddrs: v1EndpointSettings.DnsSetting.ServerIpAddrs,
					Domain:        v1EndpointSettings.DnsSetting.Domain,
					Search:        v1EndpointSettings.DnsSetting.Search,
				}
			}
			return v0Resp, nil
		}
	}
	return nil, errUnsupportedEndpointType
}

func (w *v0ServiceWrapper) GetEndpoints(ctx context.Context, req *ncproxygrpcv0.GetEndpointsRequest) (_ *ncproxygrpcv0.GetEndpointsResponse, err error) {
	resp, err := w.s.GetEndpoints(ctx, &ncproxygrpc.GetEndpointsRequest{})
	if err != nil {
		return nil, err
	}
	v0Endpoints := make([]*ncproxygrpcv0.GetEndpointResponse, len(resp.Endpoints))
	for i, e := range resp.Endpoints {
		v0Endpoint, err := v1EndpointToV0EndpointResp(e)
		if err != nil {
			if err == errUnsupportedEndpointType {
				// ignore unsupported endpoints
				continue
			}
			return nil, err
		}
		v0Endpoints[i] = v0Endpoint
	}
	return &ncproxygrpcv0.GetEndpointsResponse{
		Endpoints: v0Endpoints,
	}, nil
}

func (w *v0ServiceWrapper) GetNetwork(ctx context.Context, req *ncproxygrpcv0.GetNetworkRequest) (_ *ncproxygrpcv0.GetNetworkResponse, err error) {
	v1Req := &ncproxygrpc.GetNetworkRequest{
		Name: req.Name,
	}
	resp, err := w.s.GetNetwork(ctx, v1Req)
	if err != nil {
		return nil, err
	}

	v0Resp, err := v1NetworkToV0NetworkResp(resp)
	if err != nil {
		if err == errUnsupportedNetworkType {
			return nil, status.Errorf(codes.NotFound, "no network with name `%s` found", req.Name)
		}
		return nil, err
	}

	return v0Resp, nil
}

func v1NetworkToV0NetworkResp(v1NetworkResp *ncproxygrpc.GetNetworkResponse) (_ *ncproxygrpcv0.GetNetworkResponse, err error) {
	if v1NetworkResp.Network != nil {
		if v1NetworkResp.Network.GetHcnNetwork() != nil {
			v0Resp := &ncproxygrpcv0.GetNetworkResponse{
				ID:   v1NetworkResp.ID,
				Name: v1NetworkResp.Network.GetHcnNetwork().Name,
			}
			return v0Resp, nil
		}
	}
	return nil, errUnsupportedNetworkType
}

func (w *v0ServiceWrapper) GetNetworks(ctx context.Context, req *ncproxygrpcv0.GetNetworksRequest) (_ *ncproxygrpcv0.GetNetworksResponse, err error) {
	resp, err := w.s.GetNetworks(ctx, &ncproxygrpc.GetNetworksRequest{})
	if err != nil {
		return nil, err
	}
	v0Networks := make([]*ncproxygrpcv0.GetNetworkResponse, len(resp.Networks))
	for i, n := range resp.Networks {
		v0Network, err := v1NetworkToV0NetworkResp(n)
		if err != nil {
			if err == errUnsupportedNetworkType {
				// ignore unsupported networks
				continue
			}
			return nil, err
		}
		v0Networks[i] = v0Network
	}
	return &ncproxygrpcv0.GetNetworksResponse{
		Networks: v0Networks,
	}, nil
}
