package overlay

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/Microsoft/hcsshim"
	"github.com/containerd/log"
	"github.com/docker/docker/libnetwork/driverapi"
	"github.com/docker/docker/libnetwork/netlabel"
	"github.com/docker/docker/libnetwork/portmapper"
	"github.com/docker/docker/libnetwork/types"
)

var (
	hostMode  bool
	networkMu sync.Mutex
)

type networkTable map[string]*network

type subnet struct {
	vni      uint32
	subnetIP *net.IPNet
	gwIP     *net.IP
}

type subnetJSON struct {
	SubnetIP string
	GwIP     string
	Vni      uint32
}

type network struct {
	id              string
	name            string
	hnsID           string
	providerAddress string
	interfaceName   string
	endpoints       endpointTable
	driver          *driver
	initEpoch       int
	initErr         error
	subnets         []*subnet
	secure          bool
	portMapper      *portmapper.PortMapper
	sync.Mutex
}

func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
	return nil, types.NotImplementedErrorf("not implemented")
}

func (d *driver) NetworkFree(id string) error {
	return types.NotImplementedErrorf("not implemented")
}

func (d *driver) CreateNetwork(ctx context.Context, id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
	var (
		networkName   string
		interfaceName string
		staleNetworks []string
	)

	if id == "" {
		return fmt.Errorf("invalid network id")
	}

	if nInfo == nil {
		return fmt.Errorf("invalid network info structure")
	}

	if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
		return types.InvalidParameterErrorf("ipv4 pool is empty")
	}

	staleNetworks = make([]string, 0)
	vnis := make([]uint32, 0, len(ipV4Data))

	existingNetwork := d.network(id)
	if existingNetwork != nil {
		log.G(ctx).Debugf("Network preexists. Deleting %s", id)
		err := d.DeleteNetwork(id)
		if err != nil {
			log.G(ctx).Errorf("Error deleting stale network %s", err.Error())
		}
	}

	n := &network{
		id:         id,
		driver:     d,
		endpoints:  endpointTable{},
		subnets:    []*subnet{},
		portMapper: portmapper.New(),
	}

	genData, ok := option[netlabel.GenericData].(map[string]string)

	if !ok {
		return fmt.Errorf("Unknown generic data option")
	}

	for label, value := range genData {
		switch label {
		case "com.docker.network.windowsshim.networkname":
			networkName = value
		case "com.docker.network.windowsshim.interface":
			interfaceName = value
		case "com.docker.network.windowsshim.hnsid":
			n.hnsID = value
		case netlabel.OverlayVxlanIDList:
			vniStrings := strings.Split(value, ",")
			for _, vniStr := range vniStrings {
				vni, err := strconv.Atoi(vniStr)
				if err != nil {
					return fmt.Errorf("invalid vxlan id value %q passed", vniStr)
				}

				vnis = append(vnis, uint32(vni))
			}
		}
	}

	// If we are getting vnis from libnetwork, either we get for
	// all subnets or none.
	if len(vnis) < len(ipV4Data) {
		return fmt.Errorf("insufficient vnis(%d) passed to overlay. Windows driver requires VNIs to be prepopulated", len(vnis))
	}

	for i, ipd := range ipV4Data {
		s := &subnet{
			subnetIP: ipd.Pool,
			gwIP:     &ipd.Gateway.IP,
		}

		if len(vnis) != 0 {
			s.vni = vnis[i]
		}

		d.Lock()
		for _, network := range d.networks {
			found := false
			for _, sub := range network.subnets {
				if sub.vni == s.vni {
					staleNetworks = append(staleNetworks, network.id)
					found = true
					break
				}
			}
			if found {
				break
			}
		}
		d.Unlock()

		n.subnets = append(n.subnets, s)
	}

	for _, staleNetwork := range staleNetworks {
		d.DeleteNetwork(staleNetwork)
	}

	n.name = networkName
	if n.name == "" {
		n.name = id
	}

	n.interfaceName = interfaceName

	if nInfo != nil {
		if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
			return err
		}
	}

	d.addNetwork(n)

	err := d.createHnsNetwork(n)

	if err != nil {
		d.deleteNetwork(id)
	} else {
		genData["com.docker.network.windowsshim.hnsid"] = n.hnsID
	}

	return err
}

func (d *driver) DeleteNetwork(nid string) error {
	if nid == "" {
		return fmt.Errorf("invalid network id")
	}

	n := d.network(nid)
	if n == nil {
		return types.ForbiddenErrorf("could not find network with id %s", nid)
	}

	_, err := hcsshim.HNSNetworkRequest(http.MethodDelete, n.hnsID, "")
	if err != nil {
		return types.ForbiddenErrorf("%v", err)
	}

	d.deleteNetwork(nid)

	return nil
}

func (d *driver) ProgramExternalConnectivity(_ context.Context, nid, eid string, options map[string]interface{}) error {
	return nil
}

func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
	return nil
}

func (d *driver) addNetwork(n *network) {
	d.Lock()
	d.networks[n.id] = n
	d.Unlock()
}

func (d *driver) deleteNetwork(nid string) {
	d.Lock()
	delete(d.networks, nid)
	d.Unlock()
}

func (d *driver) network(nid string) *network {
	d.Lock()
	defer d.Unlock()
	return d.networks[nid]
}

// func (n *network) restoreNetworkEndpoints() error {
// 	log.G(ctx).Infof("Restoring endpoints for overlay network: %s", n.id)

// 	hnsresponse, err := hcsshim.HNSListEndpointRequest(http.MethodGet, "", "")
// 	if err != nil {
// 		return err
// 	}

// 	for _, endpoint := range hnsresponse {
// 		if endpoint.VirtualNetwork != n.hnsID {
// 			continue
// 		}

// 		ep := n.convertToOverlayEndpoint(&endpoint)

// 		if ep != nil {
// 			log.G(ctx).Debugf("Restored endpoint:%s Remote:%t", ep.id, ep.remote)
// 			n.addEndpoint(ep)
// 		}
// 	}

// 	return nil
// }

func (n *network) convertToOverlayEndpoint(v *hcsshim.HNSEndpoint) *endpoint {
	ep := &endpoint{
		id:        v.Name,
		profileID: v.Id,
		nid:       n.id,
		remote:    v.IsRemoteEndpoint,
	}

	mac, err := net.ParseMAC(v.MacAddress)
	if err != nil {
		return nil
	}

	ep.mac = mac
	ep.addr = &net.IPNet{
		IP:   v.IPAddress,
		Mask: net.CIDRMask(32, 32),
	}

	return ep
}

func (d *driver) createHnsNetwork(n *network) error {
	subnets := []hcsshim.Subnet{}

	for _, s := range n.subnets {
		subnet := hcsshim.Subnet{
			AddressPrefix: s.subnetIP.String(),
		}

		if s.gwIP != nil {
			subnet.GatewayAddress = s.gwIP.String()
		}

		vsidPolicy, err := json.Marshal(hcsshim.VsidPolicy{
			Type: "VSID",
			VSID: uint(s.vni),
		})
		if err != nil {
			return err
		}

		subnet.Policies = append(subnet.Policies, vsidPolicy)
		subnets = append(subnets, subnet)
	}

	network := &hcsshim.HNSNetwork{
		Name:               n.name,
		Type:               d.Type(),
		Subnets:            subnets,
		NetworkAdapterName: n.interfaceName,
		AutomaticDNS:       true,
	}

	configurationb, err := json.Marshal(network)
	if err != nil {
		return err
	}

	configuration := string(configurationb)
	log.G(context.TODO()).Infof("HNSNetwork Request =%v", configuration)

	hnsresponse, err := hcsshim.HNSNetworkRequest(http.MethodPost, "", configuration)
	if err != nil {
		return err
	}

	n.hnsID = hnsresponse.Id
	n.providerAddress = hnsresponse.ManagementIP

	return nil
}

// contains return true if the passed ip belongs to one the network's
// subnets
func (n *network) contains(ip net.IP) bool {
	for _, s := range n.subnets {
		if s.subnetIP.Contains(ip) {
			return true
		}
	}

	return false
}

// getSubnetforIP returns the subnet to which the given IP belongs
func (n *network) getSubnetforIP(ip *net.IPNet) *subnet {
	for _, s := range n.subnets {
		// first check if the mask lengths are the same
		i, _ := s.subnetIP.Mask.Size()
		j, _ := ip.Mask.Size()
		if i != j {
			continue
		}
		if s.subnetIP.Contains(ip.IP) {
			return s
		}
	}
	return nil
}

// getMatchingSubnet return the network's subnet that matches the input
func (n *network) getMatchingSubnet(ip *net.IPNet) *subnet {
	if ip == nil {
		return nil
	}
	for _, s := range n.subnets {
		// first check if the mask lengths are the same
		i, _ := s.subnetIP.Mask.Size()
		j, _ := ip.Mask.Size()
		if i != j {
			continue
		}
		if s.subnetIP.IP.Equal(ip.IP) {
			return s
		}
	}
	return nil
}
