package asaModels

import (
	"fmt"
	"net"
	"regexp"
	"strconv"
	"strings"

	"github.com/sirupsen/logrus"
)

var (
	manualNatRe    = regexp.MustCompile(`(?is)manual\s+nat\s+policies\s+\(section\s+1\)\n(.*?)\n(\n|\S+#)`)
	autoNatRe      = regexp.MustCompile(`(?is)auto\s+nat\s+policies\s+\(section\s+2\)\n(.*?)\n(\n|\S+#)`)
	afterAutoNatRe = regexp.MustCompile(`(?is)manual\s+nat\s+policies\s+\(section\s+3\)\n(.*?)\n(\n|\S+#)`)

	manualNATParser = regexp.MustCompile(`(?i)(\d+)\s+\((\S+)\)\s+to\s+\((\S+)\)\s+source\s+(\S+)\s+(\S+)\s+(\S+)\s+` +
		`destination\s+(\S+)\s+(\S+)\s+(\S+)(\s+(no-proxy-arp|)\s*(route-lookup|)|)\n\s+translate_hits\s+=\s+(\d+),` +
		`\s+untranslate_hits\s+=\s+(\d+)\n\s+Source\s+-\s+Origin:\s+(.*)\s+Translated:\s+(.*)\n\s+Destination\s+` +
		`-\s+Origin:\s+(.*)Translated:\s+(.*)`)
	autoNATParser = regexp.MustCompile(`(?i)(\d+)\s+\((\S+)\)\s+to\s+\((\S+)\)\s+source\s+(\S+)\s+(\S+)\s+(\S+)\s*(dns|)` +
		`\n\s+translate_hits\s+=\s+(\d+),\s+untranslate_hits\s+=\s+(\d+)\n\s+Source\s+-\s+Origin:\s+(.*)\s+Translated:\s+(.*)`)
)

type (
	NatStatus struct {
		ManualNat    []ManualNat    `json:"manual_nat"`
		AutoNat      []AutoNat      `json:"auto_nat"`
		AfterAutoNat []AfterAutoNat `json:"after_auto_nat"`
	}
	ManualNat struct {
		Line            int       `json:"line"`
		RealIfc         string    `json:"real_interface"`
		MappedIfc       string    `json:"mapped_interface"`
		Source          NatObject `json:"source"`
		Destination     NatObject `json:"destination"`
		TranslateHits   string    `json:"translate_hits"`
		UnTranslateHits string    `json:"untranslate_hits"`
		NoProxyArp      bool      `json:"no_proxy_arp"`
		RouteLookup     bool      `json:"route_lookup"`
	}
	AutoNat struct {
		Line            int       `json:"line"`
		RealIfc         string    `json:"real_interface"`
		MappedIfc       string    `json:"mapped_interface"`
		Source          NatObject `json:"source"`
		TranslateHits   string    `json:"translate_hits"`
		UnTranslateHits string    `json:"untranslate_hits"`
		DNS             bool      `json:"dns"`
	}
	AfterAutoNat struct {
		Line            int       `json:"line"`
		RealIfc         string    `json:"real_interface"`
		MappedIfc       string    `json:"mapped_interface"`
		Source          NatObject `json:"source"`
		TranslateHits   string    `json:"translate_hits"`
		UnTranslateHits string    `json:"untranslate_hits"`
	}
	NatObject struct {
		Type           string       `json:"type"`
		RealObj        string       `json:"real_object"`
		MappedObj      string       `json:"mapped_object"`
		OriginNets     []*net.IPNet `json:"origin_networks"`
		TranslatedNets []*net.IPNet `json:"translated_networks"`
	}
)

func (n *NatStatus) Parse(s string, names Names) {
	manualNATs := manualNatRe.FindAllStringSubmatch(s, -1)
	if len(manualNATs) == 1 {
		for _, nat := range manualNATParser.FindAllStringSubmatch(manualNATs[0][1], -1) {
			n.ManualNat = append(n.ManualNat, ManualNat{
				Line: func() int {
					i, err := strconv.Atoi(nat[1])
					if err != nil {
						logrus.Warnf("asaModels:NatStatus:Parse:unable to parse %v", nat[1])
						return 0
					}
					return i
				}(),
				RealIfc:   nat[2],
				MappedIfc: nat[3],
				Source: NatObject{
					Type:           nat[4],
					RealObj:        nat[5],
					MappedObj:      nat[6],
					OriginNets:     parseNetworks(nat[15], names),
					TranslatedNets: parseNetworks(nat[16], names),
				},
				Destination: NatObject{
					Type:           nat[7],
					RealObj:        nat[8],
					MappedObj:      nat[9],
					OriginNets:     parseNetworks(nat[17], names),
					TranslatedNets: parseNetworks(nat[18], names),
				},
				NoProxyArp: func() bool {
					if strings.Contains(nat[11], "no-proxy-arp") {
						return true
					}
					return false
				}(),
				RouteLookup: func() bool {
					if strings.Contains(nat[12], "route-lookup") {
						return true
					}
					return false
				}(),
				TranslateHits:   nat[13],
				UnTranslateHits: nat[14],
			})
		}
	} else {
		logrus.Errorf("asaModels:NatStatus:Parse:unable to parse manual-nat config:%v", manualNATs)
	}

	autoNATs := autoNatRe.FindAllStringSubmatch(s, -1)
	if len(autoNATs) == 1 {
		for _, nat := range autoNATParser.FindAllStringSubmatch(autoNATs[0][1], -1) {
			n.AutoNat = append(n.AutoNat, AutoNat{
				Line: func() int {
					i, err := strconv.Atoi(nat[1])
					if err != nil {
						logrus.Warnf("asaModels:NatStatus:Parse:unable to parse %v", nat[1])
						return 0
					}
					return i
				}(),
				RealIfc:   nat[2],
				MappedIfc: nat[3],
				Source: NatObject{
					Type:           nat[4],
					RealObj:        nat[5],
					MappedObj:      nat[6],
					OriginNets:     parseNetworks(nat[10], names),
					TranslatedNets: parseNetworks(nat[11], names),
				},
				DNS: func() bool {
					if strings.Contains(nat[7], "dns") {
						return true
					}
					return false
				}(),
				TranslateHits:   nat[8],
				UnTranslateHits: nat[9],
			})
		}
	} else {
		logrus.Errorf("asaModels:NatStatus:Parse:unable to parse auto-nat config:%v", autoNATs)
	}

	afterAutoNATs := afterAutoNatRe.FindAllStringSubmatch(s, -1)
	if len(afterAutoNATs) == 1 {
		for _, nat := range autoNATParser.FindAllStringSubmatch(afterAutoNATs[0][1], -1) {
			n.AfterAutoNat = append(n.AfterAutoNat, AfterAutoNat{
				Line: func() int {
					i, err := strconv.Atoi(nat[1])
					if err != nil {
						logrus.Warnf("asaModels:NatStatus:Parse:unable to parse %v", nat[1])
						return 0
					}
					return i
				}(),
				RealIfc:   nat[2],
				MappedIfc: nat[3],
				Source: NatObject{
					Type:           nat[4],
					RealObj:        nat[5],
					MappedObj:      nat[6],
					OriginNets:     parseNetworks(nat[10], names),
					TranslatedNets: parseNetworks(nat[11], names),
				},
				TranslateHits:   nat[8],
				UnTranslateHits: nat[9],
			})
		}
	} else {
		logrus.Errorf("asaModels:NatStatus:Parse:unable to parse after-auto-nat config:%v", afterAutoNATs)
	}
}

func parseNetworks(s string, names Names) (networks []*net.IPNet) {
	for _, n := range strings.Split(strings.Replace(s, " ", "", -1), ",") {
		if n == "" {
			continue
		}
		// If the network is a range of IPs, convert it into a CIDR to parse as a net.IPNet object
		if strings.Contains(n, "-") {
			r := strings.Split(n, "-")
			networks = append(networks, rangeToCIDR(names.NameIP(r[0]), r[1]))
			continue
		}
		r := strings.Split(n, "/")
		_, parsed, err := net.ParseCIDR(fmt.Sprintf("%s/%s", names.NameIP(r[0]), r[1]))
		if err != nil {
			logrus.Errorf("asaModels:NatStatus:parseNetworks:unable to parse network %s in %s::%s", n, s, err)
			continue
		}
		networks = append(networks, parsed)
	}
	return
}

func rangeToCIDR(start, end string) *net.IPNet {
	for l := 32; l >= 0; l-- {
		mask := net.CIDRMask(l, 32)
		na := net.ParseIP(start).Mask(mask)
		n := net.IPNet{IP: na, Mask: mask}
		if n.Contains(net.ParseIP(end)) {
			_, cidr, err := net.ParseCIDR(fmt.Sprintf("%v/%v", na, l))
			if err != nil {
				logrus.Errorf("asaModels:NatStatus:rangeToCIDR:unable to parse network %v/%v", na, l)
				return nil
			}
			return cidr
		}
	}
	return nil
}
