package plugin

import (
	"fmt"
	"net"

	log "github.com/golang/glog"

	osclient "github.com/openshift/origin/pkg/client"
	osconfigapi "github.com/openshift/origin/pkg/cmd/server/api"
	osapi "github.com/openshift/origin/pkg/sdn/api"
	"github.com/openshift/origin/pkg/util/netutils"

	kapi "k8s.io/kubernetes/pkg/api"
	kapiunversioned "k8s.io/kubernetes/pkg/api/unversioned"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	kerrors "k8s.io/kubernetes/pkg/util/errors"
)

type OsdnMaster struct {
	kClient         *kclientset.Clientset
	osClient        *osclient.Client
	networkInfo     *NetworkInfo
	subnetAllocator *netutils.SubnetAllocator
	vnids           *masterVNIDMap
}

func StartMaster(networkConfig osconfigapi.MasterNetworkConfig, osClient *osclient.Client, kClient *kclientset.Clientset) error {
	if !osapi.IsOpenShiftNetworkPlugin(networkConfig.NetworkPluginName) {
		return nil
	}

	log.Infof("Initializing SDN master of type %q", networkConfig.NetworkPluginName)

	master := &OsdnMaster{
		kClient:  kClient,
		osClient: osClient,
	}

	var err error
	master.networkInfo, err = parseNetworkInfo(networkConfig.ClusterNetworkCIDR, networkConfig.ServiceNetworkCIDR)
	if err != nil {
		return err
	}

	createConfig := false
	updateConfig := false
	cn, err := master.osClient.ClusterNetwork().Get(osapi.ClusterNetworkDefault)
	if err == nil {
		if master.networkInfo.ClusterNetwork.String() != cn.Network ||
			networkConfig.HostSubnetLength != cn.HostSubnetLength ||
			master.networkInfo.ServiceNetwork.String() != cn.ServiceNetwork ||
			networkConfig.NetworkPluginName != cn.PluginName {
			updateConfig = true
		}
	} else {
		cn = &osapi.ClusterNetwork{
			TypeMeta:   kapiunversioned.TypeMeta{Kind: "ClusterNetwork"},
			ObjectMeta: kapi.ObjectMeta{Name: osapi.ClusterNetworkDefault},
		}
		createConfig = true
	}
	if createConfig || updateConfig {
		if err = master.validateNetworkConfig(); err != nil {
			return err
		}
		size, len := master.networkInfo.ClusterNetwork.Mask.Size()
		if networkConfig.HostSubnetLength < 1 || networkConfig.HostSubnetLength >= uint32(len-size) {
			return fmt.Errorf("invalid HostSubnetLength %d for network %s (must be from 1 to %d)", networkConfig.HostSubnetLength, networkConfig.ClusterNetworkCIDR, len-size)
		}
		cn.Network = master.networkInfo.ClusterNetwork.String()
		cn.HostSubnetLength = networkConfig.HostSubnetLength
		cn.ServiceNetwork = master.networkInfo.ServiceNetwork.String()
		cn.PluginName = networkConfig.NetworkPluginName
	}

	if createConfig {
		cn, err := master.osClient.ClusterNetwork().Create(cn)
		if err != nil {
			return err
		}
		log.Infof("Created ClusterNetwork %s", clusterNetworkToString(cn))
	} else if updateConfig {
		cn, err := master.osClient.ClusterNetwork().Update(cn)
		if err != nil {
			return err
		}
		log.Infof("Updated ClusterNetwork %s", clusterNetworkToString(cn))
	}

	if err = master.SubnetStartMaster(master.networkInfo.ClusterNetwork, networkConfig.HostSubnetLength); err != nil {
		return err
	}

	if osapi.IsOpenShiftMultitenantNetworkPlugin(networkConfig.NetworkPluginName) {
		master.vnids = newMasterVNIDMap()

		if err = master.VnidStartMaster(); err != nil {
			return err
		}
	}

	return nil
}

func (master *OsdnMaster) validateNetworkConfig() error {
	hostIPNets, _, err := netutils.GetHostIPNetworks([]string{TUN})
	if err != nil {
		return err
	}

	ni := master.networkInfo
	errList := []error{}

	// Ensure cluster and service network don't overlap with host networks
	for _, ipNet := range hostIPNets {
		if ipNet.Contains(ni.ClusterNetwork.IP) {
			errList = append(errList, fmt.Errorf("Error: Cluster IP: %s conflicts with host network: %s", ni.ClusterNetwork.IP.String(), ipNet.String()))
		}
		if ni.ClusterNetwork.Contains(ipNet.IP) {
			errList = append(errList, fmt.Errorf("Error: Host network with IP: %s conflicts with cluster network: %s", ipNet.IP.String(), ni.ClusterNetwork.String()))
		}
		if ipNet.Contains(ni.ServiceNetwork.IP) {
			errList = append(errList, fmt.Errorf("Error: Service IP: %s conflicts with host network: %s", ni.ServiceNetwork.String(), ipNet.String()))
		}
		if ni.ServiceNetwork.Contains(ipNet.IP) {
			errList = append(errList, fmt.Errorf("Error: Host network with IP: %s conflicts with service network: %s", ipNet.IP.String(), ni.ServiceNetwork.String()))
		}
	}

	// Ensure each host subnet is within the cluster network
	subnets, err := master.osClient.HostSubnets().List(kapi.ListOptions{})
	if err != nil {
		return fmt.Errorf("Error in initializing/fetching subnets: %v", err)
	}
	for _, sub := range subnets.Items {
		subnetIP, _, _ := net.ParseCIDR(sub.Subnet)
		if subnetIP == nil {
			errList = append(errList, fmt.Errorf("Failed to parse network address: %s", sub.Subnet))
			continue
		}
		if !ni.ClusterNetwork.Contains(subnetIP) {
			errList = append(errList, fmt.Errorf("Error: Existing node subnet: %s is not part of cluster network: %s", sub.Subnet, ni.ClusterNetwork.String()))
		}
	}

	// Ensure each service is within the services network
	services, err := master.kClient.Core().Services(kapi.NamespaceAll).List(kapi.ListOptions{})
	if err != nil {
		return err
	}
	for _, svc := range services.Items {
		if !ni.ServiceNetwork.Contains(net.ParseIP(svc.Spec.ClusterIP)) {
			errList = append(errList, fmt.Errorf("Error: Existing service with IP: %s is not part of service network: %s", svc.Spec.ClusterIP, ni.ServiceNetwork.String()))
		}
	}

	return kerrors.NewAggregate(errList)
}