package pkg

import (
	"bytes"
	"errors"
	"fmt"

	"github.rackspace.com/gorax/clients/sshAPI"
)

// Common methods that are shared amongst POST/PUT for AFM
func afmRuleBuild(r *Rule, m string) (string, string) {

	var srcitems bytes.Buffer
	if r.RuleList != nil {
		srcitems.WriteString(fmt.Sprintf("rule-list %s ", *r.RuleList))
		return srcitems.String(), ""
	}

	if r.Source != nil {
		srcitems.WriteString("source { ")
		if r.Source.Address != nil {
			srcitems.WriteString(fmt.Sprintf("addresses %s { ", m))
			for _, s := range r.Source.Address {
				srcitems.WriteString(fmt.Sprintf("%s { }", *s.Address))
			}
			srcitems.WriteString(" } ")
		}
		if r.Source.Vlans != nil {
			srcitems.WriteString(fmt.Sprintf("vlans %s { ", m))
			for _, s := range r.Source.Vlans {
				srcitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			srcitems.WriteString(" } ")
		}
		if r.Source.Ports != nil {
			srcitems.WriteString(fmt.Sprintf("ports %s { ", m))
			for _, s := range r.Source.Ports {
				srcitems.WriteString(fmt.Sprintf("%s { }", *s.Port))
			}
			srcitems.WriteString(" } ")
		}
		if r.Source.PortList != nil {
			srcitems.WriteString(fmt.Sprintf("port-lists %s { ", m))
			for _, s := range r.Source.PortList {
				srcitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			srcitems.WriteString(" } ")
		}
		if r.Source.AddrList != nil {
			srcitems.WriteString(fmt.Sprintf("address-lists %s { ", m))
			for _, s := range r.Source.AddrList {
				srcitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			srcitems.WriteString(" } ")
		}
		srcitems.WriteString("}")
	}

	var dstitems bytes.Buffer
	if r.Dest != nil {
		dstitems.WriteString("destination { ")
		if r.Dest.Address != nil {
			dstitems.WriteString(fmt.Sprintf("addresses %s { ", m))
			for _, s := range r.Dest.Address {
				dstitems.WriteString(fmt.Sprintf("%s { }", *s.Address))
			}
			dstitems.WriteString(" } ")
		}
		if r.Dest.Vlans != nil {
			dstitems.WriteString(fmt.Sprintf("vlans %s { ", m))
			for _, s := range r.Dest.Vlans {
				dstitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			dstitems.WriteString(" } ")
		}
		if r.Dest.Ports != nil {
			dstitems.WriteString(fmt.Sprintf("ports %s { ", m))
			for _, s := range r.Dest.Ports {
				dstitems.WriteString(fmt.Sprintf("%s { }", *s.Port))
			}
			dstitems.WriteString(" } ")
		}
		if r.Dest.PortList != nil {
			dstitems.WriteString(fmt.Sprintf("port-lists %s { ", m))
			for _, s := range r.Dest.PortList {
				dstitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			dstitems.WriteString(" } ")
		}
		if r.Dest.AddrList != nil {
			dstitems.WriteString(fmt.Sprintf("address-lists %s { ", m))
			for _, s := range r.Dest.AddrList {
				dstitems.WriteString(fmt.Sprintf("%s ", *s))
			}
			dstitems.WriteString(" } ")
		}
		dstitems.WriteString("}")
	}
	return srcitems.String(), dstitems.String()

}

func afmRuleValidation(r *Rule, p string) error {
	if r.Name == nil {
		return errors.New(fmt.Sprintf("Rule name required for rule-list %s", p))
	}
	if r.RuleList == nil {
		if r.Action == nil {
			return errors.New(fmt.Sprintf("Rule action is required for rule-list %s", p))
		}
		if r.Source == nil && r.Dest == nil {
			return errors.New(fmt.Sprintf("Either a source AND/OR destination must be defined for rule-list %s", p))
		}
	}
	return nil
}

// AFMPolicyListsPatch : Append to an existing AFM Policy (PATCH)
func (f5 *F5) AFMPolicyListsPatch(pls map[string]*PolicyList) error {
	m := "add"
	if len(pls) == 0 {
		return errors.New("no AFM policy has been defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range pls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall policy %s rules modify { %s }", name, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*PolicyList
	f5.Config.AFMPolicyLists = s
	return nil
}

func (f5 *F5) AFMAddressListsPost(a map[string]*AddressList) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	for name, al := range a {
		var desc string
		var addresses bytes.Buffer
		if al.Desc != "" {
			desc = fmt.Sprintf("description '%s'", al.Desc)
		}
		if al.Addresses != nil {
			addresses.WriteString(" addresses replace-all-with { ")
			for _, ad := range al.Addresses {
				addresses.WriteString(fmt.Sprintf(" %s { }", ad))
			}
			addresses.WriteString(" } ")
		}

		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("create /security firewall address-list %s { %s %s }", name, addresses.String(), desc), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*AddressList
	f5.Config.AFMAddressLists = s
	return nil
}

func (f5 *F5) AFMAddressListsPut(a map[string]*AddressList) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	for name, al := range a {
		var desc string
		var addresses bytes.Buffer
		if al.Desc == "" {
			desc = fmt.Sprintf("description none")
		} else {
			desc = fmt.Sprintf("description '%s'", al.Desc)
		}
		if al.Addresses != nil {
			addresses.WriteString(" addresses replace-all-with { ")
			for _, ad := range al.Addresses {
				addresses.WriteString(fmt.Sprintf(" %s { }", ad))
			}
			addresses.WriteString(" } ")
		}

		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall address-list %s { %s %s }", name, addresses.String(), desc), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	var s []*AddressList
	f5.Config.AFMAddressLists = s
	return nil
}

func (f5 *F5) AFMAddressListsPatch(a map[string]*AddressList) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	for name, al := range a {
		var desc string
		var addresses bytes.Buffer
		if al.Desc == "" {
			desc = fmt.Sprintf("description none")
		} else {
			desc = fmt.Sprintf("description '%s'", al.Desc)
		}

		if al.Addresses != nil {
			addresses.WriteString(" addresses add { ")
			for _, ad := range al.Addresses {
				addresses.WriteString(fmt.Sprintf(" %s { }", ad))
			}
			addresses.WriteString(" } ")
		}

		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall address-list %s { %s %s }", name, addresses.String(), desc), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	var s []*AddressList
	f5.Config.AFMAddressLists = s
	return nil
}

func (f5 *F5) AFMAddressListsDelete(a string) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("delete /security firewall address-list %s", a), 10, promptRegex))

	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*AddressList
	f5.Config.AFMAddressLists = s
	return nil
}

func (f5 *F5) AFMRuleListsPost(rls map[string]*RuleList) error {
	m := "replace-all-with"
	if len(rls) == 0 {
		return errors.New("no rule-list defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range rls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("create /security firewall rule-list %s rules %s { %s }", name, m, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*RuleList
	f5.Config.AFMRuleLists = s
	return nil
}

func (f5 *F5) AFMRuleListsPut(rls map[string]*RuleList) error {
	m := "replace-all-with"
	if len(rls) == 0 {
		return errors.New("no rule-list defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range rls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall rule-list %s rules %s { %s }", name, m, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*RuleList
	f5.Config.AFMRuleLists = s
	return nil
}

func (f5 *F5) AFMRuleListsPatch(rls map[string]*RuleList) error {
	m := "add"
	if len(rls) == 0 {
		return errors.New("no rule-list defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range rls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall rule-list %s rules modify { %s }", name, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*RuleList
	f5.Config.AFMRuleLists = s
	return nil
}

func (f5 *F5) AFMRuleListsDelete(a string) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("delete /security firewall rule-list %s", a), 10, promptRegex))

	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*RuleList
	f5.Config.AFMRuleLists = s
	return nil
}

// AFMPolicyListsPut : Replace an existing AFM policy with new values (Overwrite PUT)
func (f5 *F5) AFMPolicyListsPut(pls map[string]*PolicyList) error {
	m := "replace-all-with"
	if len(pls) == 0 {
		return errors.New("no AFM policy has been defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range pls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("modify /security firewall policy %s rules %s { %s }", name, m, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*PolicyList
	f5.Config.AFMPolicyLists = s
	return nil
}

// AFM Policy POST method. createF5 an AFM policy
func (f5 *F5) AFMPolicyListsPost(pls map[string]*PolicyList) error {
	m := "replace-all-with"
	if len(pls) == 0 {
		return errors.New("no AFM policy has been defined")
	}
	var cmd []*sshAPI.Command
	for name, rl := range pls {
		var rules bytes.Buffer
		for _, r := range rl.Rules {
			if err := afmRuleValidation(r, name); err != nil {
				return err
			}
			var ipproto string
			if r.IpProto != nil {
				ipproto = fmt.Sprintf("ip-protocol %s", *r.IpProto)
			}
			var action string
			if r.Action != nil {
				action = fmt.Sprintf("action %s", *r.Action)
			}
			src, dst := afmRuleBuild(r, m)
			rules.WriteString(fmt.Sprintf(" %s { %s %s %s %s }", *r.Name, action, ipproto, src, dst))

		}
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("create /security firewall policy %s rules %s { %s }", name, m, rules.String()), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*PolicyList
	f5.Config.AFMPolicyLists = s
	return nil
}

// DELETE an AFM Policy by name
func (f5 *F5) AFMPolicyDelete(a string) error {
	if len(a) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("delete /security firewall policy %s", a), 10, promptRegex))

	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	for _, resp := range res.Commands {
		if f5Resp := cmdResponseRegex.FindStringSubmatch(*resp.Response); f5Resp != nil {
			return errors.New(f5Resp[1])
		}
	}
	f5.AppendCommands(res)
	var s []*PolicyList
	f5.Config.AFMPolicyLists = s
	return nil
}
