package asaModels

import (
	"fmt"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.rackspace.com/gorax/tools/util"
)

const (
	regexPFS = `%d set (?:pfs group|pfs)(?:(\d+)|)\n`
)

var (
	regexCrypto            = regexp.MustCompile(`crypto\s+map\s+(\S+)\s+([0-9].*)\n`)
	regexCryptoNum         = regexp.MustCompile(`(\n|^)([0-9]+)\s+`)
	regexIKE1Policy        = regexp.MustCompile(`(|c)rypto\s+ikev1\s+policy\s+([0-9]+)\n((.|\n)*?)(\nc|$)`)
	regexIKE2Policy        = regexp.MustCompile(`(|c)rypto\s+ikev2\s+policy\s+([0-9]+)\n((.|\n)*?)(\nc|$)`)
	regexIKEEnc            = regexp.MustCompile(`(?i)encryption\s+(\S+)`)
	regexIKEHash           = regexp.MustCompile(`(?i)hash\s+(\S+)`)
	regexIKEGroup          = regexp.MustCompile(`(?i)group\s+([0-9]+)`)
	regexIKE2Group         = regexp.MustCompile(`(?i)group\s+(.*)`)
	regexIKE1Lifetime      = regexp.MustCompile(`(?i)lifetime\s+([0-9]+)`)
	regexIKE1Auth          = regexp.MustCompile(`(?i)authentication\s+(\S+)`)
	regexIKE1Xform         = regexp.MustCompile(`(?i)crypto\s+ipsec\s+ikev1\s+transform-set\s+(\S+)\s+(.*?)\n`)
	regexIKE2IPSecProposal = regexp.MustCompile(`(|c)rypto ipsec ikev2 ipsec-proposal (\S+)\n((.|\n)*?)(\nc|$)`)
	regexIKE1EncSet        = regexp.MustCompile(`(?i)(esp-aes-192|esp-aes-256|esp-des|esp-null|esp-3des|esp-aes)`)
	regexIKEV1AuthSet      = regexp.MustCompile(`(?i)(esp-sha-hmac|esp-md5-hmac|esp-none)`)
	regexIKEEnInt          = regexp.MustCompile(`(?i)crypto\s+(ikev1|ikev2|isakmp)\s+enable\s+(\S+)`)
	regexIKE2Integrity     = regexp.MustCompile(`(?i)integrity\s+(.*)`)
	regexIKE2Encryption    = regexp.MustCompile(`(?i)encryption\s+(.*)`)
	regexIKE2PRF           = regexp.MustCompile(`(?i)prf\s+(.*)`)
	regexIKE2Lifetime      = regexp.MustCompile(`(?i)lifetime seconds\s+([0-9]+)`)
	regexTrustpoint        = regexp.MustCompile(`(?i)crypto ca trustpoint (\S+)\n(\s(.*)\n)*`)
)

type (
	Crypto struct {
		CryptoMap           []*CryptoMap         `json:"crypto-maps"`
		IKE1Policies        []IKE1Policy         `json:"ikev1_policies"`
		IKE2Policies        []IKE2Policy         `json:"ikev2_policies"`
		IKEV1XFormSets      []IKEV1XFormSet      `json:"ikev1_transform_sets"`
		IKEV2IPSecProposals []IKEV2IPSecProposal `json:"ikev2_ipsec_proposals"`
		IKEV1EnableInt      []string             `json:"crypto_ike_enable"`
		IKEV2EnableInt      []string             `json:"crypto_ikev2_enable"`
		TrustPoints         []TrustPoint         `json:"trustpoints"`
		Keys                CryptoKeys           `json:"crypto_keys"`
	}

	CryptoKeys struct {
		MyPubKeys PubKeys `json:"mypubkey"`
	}

	PubKeys struct {
		ECDSA []ECDSAKey `json:"ecdsa"`
		RSA   []RSAKey   `json:"rsa"`
	}

	ECDSAKey struct{}

	RSAKey struct {
		Name        string `json:"key_name"`
		Usage       string `json:"usage"`
		Storage     string `json:"storage"`
		ModulusSize int    `json:"modulus_size"`
		KeyData     string `json:"key_data"`
		Generated   string `json:"generated_time"`
	}

	TrustPoint struct {
		Name        string `json:"name"`
		Enrollment  string `json:"enrollment"`
		FQDN        string `json:"fqdn"`
		SubjectName string `json:"subject_name"`
		Keypair     string `json:"keypair"`
		CRL         string `json:"crl"`
	}

	IKEEnable struct {
		Type      string `json:"type"`
		Interface string `json:"interface"`
	}

	CryptoMap struct {
		Name      string      `json:"name"`
		MapEntry  []*MapEntry `json:"maps"`
		Interface *string     `json:"interface"`
	}

	MapEntry struct {
		Sequence      int      `json:"sequence"`
		Match         *string  `json:"match_address"`
		Peer          []string `json:"peers"`
		TransformSet  *string  `json:"transform-set"`
		Ikev2Proposal *string  `json:"ikev2-proposal"`
		Dynamic       *string  `json:"dynamic"`
		PFS           string   `json:"pfs"`
	}

	IKE1Policy struct {
		Num      *int    `json:"policy_number"`
		Auth     *string `json:"authentication"`
		Enc      *string `json:"encryption"`
		Hash     *string `json:"hash"`
		Group    *int    `json:"group"`
		LifeTime *int    `json:"lifetime"`
	}

	IKE2Policy struct {
		Num       int    `json:"policy_number"`
		Enc       string `json:"encryption"`
		Integrity string `json:"integrity"`
		Group     []int  `json:"group"`
		PRF       string `json:"prf"`
		LifeTime  int    `json:"lifetime"`
	}

	IKEV1XFormSet struct {
		Name       *string `json:"name"`
		Encryption *string `json:"encryption"`
		Auth       *string `json:"authentication"`
	}

	IKEV2IPSecProposal struct {
		Name       string  `json:"name"`
		Encryption *string `json:"encryption"`
		Integrity  *string `json:"integrity"`
	}
)

func (c *CryptoKeys) ParseRSA(raw string) error {
	var (
		keys           []RSAKey
		generatedRegex = regexp.MustCompile(`Key pair was generated at: (.*)`)
		keyNameRegex   = regexp.MustCompile(`Key name: (.*)`)
		usuageRegex    = regexp.MustCompile(`Usage: (.*)`)
		modulusRegex   = regexp.MustCompile(`Modulus Size \(bits\): (.*)`)
		storageRegex   = regexp.MustCompile(`Storage: (.*)`)
		keyDataRegex   = regexp.MustCompile(`\s\s(.*)`)
	)

	split := strings.Split(raw, "\n")

	for _, line := range split {
		date := generatedRegex.FindStringSubmatch(line)
		if len(date) > 0 {
			// New key
			keys = append(keys, RSAKey{
				Generated: date[1],
			})
			continue
		}

		name := keyNameRegex.FindStringSubmatch(line)
		if len(name) > 0 {
			keys[len(keys)-1].Name = name[1]
			continue
		}

		usage := usuageRegex.FindStringSubmatch(line)
		if len(usage) > 0 {
			keys[len(keys)-1].Usage = usage[1]
			continue
		}

		modulus := modulusRegex.FindStringSubmatch(line)
		if len(modulus) > 0 {
			n, err := strconv.Atoi(modulus[1])
			if err != nil {
				return err
			}
			keys[len(keys)-1].ModulusSize = n
			continue
		}

		storage := storageRegex.FindStringSubmatch(line)
		if len(storage) > 0 {
			keys[len(keys)-1].Storage = storage[1]
			continue
		}

		// Skip certain lines
		if len(keys) < 1 || line == "" || strings.Contains(line, "Key Data") {
			continue
		}

		if keyDataRegex.MatchString(line) {
			if len(keys[len(keys)-1].KeyData) > 0 {
				keys[len(keys)-1].KeyData = keys[len(keys)-1].KeyData + "\r\n" + line
			} else {
				keys[len(keys)-1].KeyData = line
			}
		}
	}

	c.MyPubKeys.RSA = keys
	return nil
}

func (c *CryptoKeys) ParseECDSA(raw string) error {
	return nil
}

func FindCryptoPeer(peer string, cm []*CryptoMap) *MapEntry {
	for _, c := range cm {
		if c.Interface == nil {
			continue
		}
		for _, m := range c.MapEntry {
			for _, p := range m.Peer {
				if p == peer {
					return m
				}
			}
		}
	}
	return nil
}

//func CryptoParse(s string) *Crypto {
//	c := new(Crypto)
//	c.Parse(s)
//	return c
//}

func (c *Crypto) Parse(s string) {
	m := make(map[string][]string)

	match := regexTrustpoint.FindAllStringSubmatch(s, -1)
	if len(match) > 0 {
		for _, l := range match {
			t := parseTrustpoint(l[1], l[0])
			c.TrustPoints = append(c.TrustPoints, t)
		}
	}

	match = regexCrypto.FindAllStringSubmatch(s, -1)
	if len(match) == 0 {
		return
	}

	for _, k := range match {
		m[k[1]] = append(m[k[1]], k[2])
	}

	c.cryptoMap(&m, s)

	c.IKEParse(s)
	c.IKEXFormParse(s)

	ikeInt := regexIKEEnInt.FindAllStringSubmatch(s, -1)
	if len(ikeInt) != 0 {
		for _, i := range ikeInt {
			switch i[1] {
			case "ikev2":
				c.IKEV2EnableInt = append(c.IKEV2EnableInt, i[2])
			default:
				c.IKEV1EnableInt = append(c.IKEV1EnableInt, i[2])
			}

		}
	}

}

func parseTrustpoint(n, raw string) TrustPoint {
	var t TrustPoint
	t.Name = n

	match := regexp.MustCompile(`enrollment (\S+)`).FindStringSubmatch(raw)
	if len(match) > 1 {
		t.Enrollment = match[1]
	}

	match = regexp.MustCompile(`fqdn (\S+)`).FindStringSubmatch(raw)
	if len(match) > 1 {
		t.FQDN = match[1]
	}

	match = regexp.MustCompile(`subject-name (\S+)`).FindStringSubmatch(raw)
	if len(match) > 1 {
		t.SubjectName = match[1]
	}

	match = regexp.MustCompile(`keypair (\S+)`).FindStringSubmatch(raw)
	if len(match) > 1 {
		t.Keypair = match[1]
	}

	match = regexp.MustCompile(`crl (\S+)`).FindStringSubmatch(raw)
	if len(match) > 1 {
		t.CRL = match[1]
	}

	return t
}

func (c *Crypto) IKEXFormParse(s string) {
	match := regexIKE1Xform.FindAllStringSubmatch(s, -1)
	if len(match) != 0 {
		for _, ts := range match {
			c.IKEV1XFormSets = append(c.IKEV1XFormSets, IKEV1XFormSet{
				Name:       &ts[1],
				Encryption: util.RegexpFindStringPtr(regexIKE1EncSet, ts[2]),
				Auth:       util.RegexpFindStringPtr(regexIKEV1AuthSet, ts[2]),
			})
		}
	}
	match = regexIKE2IPSecProposal.FindAllStringSubmatch(s, -1)
	if len(match) == 0 {
		return
	}
	for _, ts := range match {
		c.IKEV2IPSecProposals = append(c.IKEV2IPSecProposals, IKEV2IPSecProposal{
			Name:       ts[2],
			Encryption: util.RegexpFindStringPtr(regexIKE2Encryption, ts[3]),
			Integrity:  util.RegexpFindStringPtr(regexIKE2Integrity, ts[3]),
		})
	}

}

func (c *Crypto) IKEParse(s string) {
	match := regexIKE1Policy.FindAllStringSubmatch(s, -1)
	if len(match) != 0 {
		for _, p := range match {
			c.IKE1Policies = append(c.IKE1Policies, IKE1Policy{
				Num:      util.AtoiPointer(p[2]),
				Auth:     util.RegexpFindStringPtr(regexIKE1Auth, p[3]),
				Enc:      util.RegexpFindStringPtr(regexIKEEnc, p[3]),
				Hash:     util.RegexpFindStringPtr(regexIKEHash, p[3]),
				Group:    util.RegexpAtoiPtr(regexIKEGroup, p[3]),
				LifeTime: util.RegexpAtoiPtr(regexIKE1Lifetime, p[3]),
			})
		}
	}
	match = regexIKE2Policy.FindAllStringSubmatch(s, -1)
	if len(match) == 0 {
		return
	}
	for _, p := range match {
		c.IKE2Policies = append(c.IKE2Policies, IKE2Policy{
			Num:       *util.AtoiPointer(p[2]),
			Enc:       *util.RegexpFindStringPtr(regexIKE2Encryption, p[3]),
			Group:     util.AtoiList(strings.Fields(*util.RegexpFindStringPtr(regexIKE2Group, p[3]))),
			Integrity: *util.RegexpFindStringPtr(regexIKE2Integrity, p[3]),
			PRF:       *util.RegexpFindStringPtr(regexIKE2PRF, p[3]),
			LifeTime:  *util.RegexpAtoiPtr(regexIKE2Lifetime, p[3]),
		})
	}
}

func (c *Crypto) cryptoMap(m *map[string][]string, fc string) {
	for n, k := range *m {
		var cm CryptoMap
		cm.Name = n
		s := strings.Join(k, "\n")
		t := make(map[string]bool)
		match := regexCryptoNum.FindAllStringSubmatch(s, -1)
		if len(match) == 0 {
			return
		}
		for _, v := range match {
			t[v[2]] = true
		}
		var mapidx []int

		for k := range t {
			mapidx = append(mapidx, util.Atoi(k))
		}

		sort.Ints(mapidx)

		for _, e := range mapidx {
			cm.seq(e, s)
		}

		re := regexp.MustCompile(fmt.Sprintf("crypto\\s+map\\s+%s\\s+interface\\s+(\\S+)", n))
		cm.Interface = util.RegexpFindStringPtr(re, fc)

		c.CryptoMap = append(c.CryptoMap, &cm)
	}

}

func (c *Crypto) HasDynamic() bool {
	for _, cm := range c.CryptoMap {
		for _, me := range cm.MapEntry {
			if me.Dynamic != nil {
				if cm.Interface != nil {
					return true
				}
			}
		}
	}
	return false
}

func (c *Crypto) FindCryptoPeer(peer string) *MapEntry {
	return FindCryptoPeer(peer, c.CryptoMap)
}

func (c *Crypto) CryptoMapInterfaces() (cm []string) {
	for _, ci := range c.CryptoMap {
		if ci.Interface != nil {
			cm = append(cm, *ci.Interface)
		}
	}
	return
}

func (c *Crypto) FindCryptoMap(cm string) *CryptoMap {
	for _, m := range c.CryptoMap {
		if m.Name == cm {
			return m
		}
	}
	return nil
}

func (c *CryptoMap) parsePfs(me *MapEntry, m int, s string) {
	p := util.RegexpFindStringPtr(regexp.MustCompile(fmt.Sprintf(regexPFS, m)), s)
	if p != nil {
		if *p == "" {
			me.PFS = "2"
		} else {
			me.PFS = *p
		}
	}
}

func (c *CryptoMap) seq(m int, s string) {
	reMatchAddr := regexp.MustCompile(fmt.Sprintf("%d\\s+match\\s+address\\s+(.*?)\\n", m))
	reMatchTrans := regexp.MustCompile(fmt.Sprintf("%d\\s+set\\s+.*?transform-set\\s+(.*?)\\n", m))
	reMatchProposal := regexp.MustCompile(fmt.Sprintf("%d\\s+set\\s+.*?ipsec-proposal\\s+(.*?)\\n", m))
	reDynamic := regexp.MustCompile(fmt.Sprintf("%d\\s+ipsec-isakmp\\s+dynamic\\s+(\\S+)", m))
	rePeers := regexp.MustCompile(fmt.Sprintf("%d\\s+set\\s+peer\\s+(.*?)(\\s+\\n|\\n|\\s+$|\\r)", m))

	me := &MapEntry{
		Sequence:      m,
		Match:         util.RegexpFindStringPtr(reMatchAddr, s),
		TransformSet:  util.RegexpFindStringPtr(reMatchTrans, s),
		Ikev2Proposal: util.RegexpFindStringPtr(reMatchProposal, s),
		Dynamic:       util.RegexpFindStringPtr(reDynamic, s),
		Peer: func() (pl []string) {
			p := util.RegexpFindStringPtr(rePeers, s)
			if p != nil {
				for _, peer := range strings.Split(*p, " ") {
					pl = append(pl, peer)
				}
			}
			return
		}(),
	}

	c.parsePfs(me, m, s)
	c.MapEntry = append(c.MapEntry, me)
}
