package pkg

import (
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

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

var (
	cmdResponseRegex = regexp.MustCompile(`(?i)\n(.*\S+)\n`)
	notFoundRegex    = regexp.MustCompile(`(?i)was not found`)
)

func (f5 *F5) AddDeviceToDeviceGroups(device string, groups []string) error {
	if len(groups) == 0 {
		return errors.New("AddDeviceToDeviceGroups/ no groups defined")
	}
	for _, group := range groups {
		cmd := fmt.Sprintf("modify /cm device-group %s devices add { %s }", group, device)
		res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, 45)
		if err != nil {
			return err
		}
		if err, _ := f5RespHandler(*res.Response); err != nil {
			return err
		}
	}
	return nil
}

func (f5 *F5) AppendCommands(c *sshAPI.SSHCommands) {
	f5.changeLog = append(f5.changeLog, c.Commands...)
}

func (f5 *F5) CliGlobalSettingsServicePatch(a *CliGlobalSettings) error {
	if !(a.Service == "name" || a.Service == "number") {
		return errors.New("must specify 'name' or 'number' for 'cli global-settings service'")
	}
	var cmd []*sshAPI.Command
	cmdString := fmt.Sprintf("modify /cli global-settings service %s", a.Service)
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 10, promptRegex))
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	return nil
}

func (f5 *F5) DataCenter() string {
	return f5.deviceInfo.DataCenter
}

func (f5 *F5) DeleteDeviceTrust() error {
	res, err := f5.sshClient.PostCommand("delete /cm trust-domain /Common/Root", defaultPrompt, 45)
	if err != nil {
		return err
	}
	if err, _ := f5RespHandler(*res.Response); err != nil {
		return err
	}
	return nil
}

func (f5 *F5) EnablePaging() error {
	f5.log.Debug("enabling paging")
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("modify cli preference pager enabled", 10, promptRegex))
	cmd = append(cmd, sshAPI.ReCommandCreate("modify cli preference display-threshold 100", 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)
	return nil
}

func (f5 *F5) DisablePaging() error {
	f5.log.Debug("disabling paging")
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("modify cli preference pager disabled", 10, promptRegex))
	cmd = append(cmd, sshAPI.ReCommandCreate("modify cli preference display-threshold 0", 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)
	return nil
}

func (f5 *F5) ForceOffline() error {
	res, err := f5.sshClient.PostCommand("run /sys failover offline", defaultPrompt, 15)
	if err != nil {
		return err
	}
	if err, _ := f5RespHandler(*res.Response); err != nil {
		return err
	}
	return nil
}

func (f5 *F5) ForceOnline() error {
	res, err := f5.sshClient.PostCommand("run /sys failover online", defaultPrompt, 15)
	if err != nil {
		return err
	}
	if err, _ := f5RespHandler(*res.Response); err != nil {
		return err
	}
	return nil
}

func (f5 *F5) FindInterfaceConfig(name string) *IntHwConfig {
	return FindInterfaceConfig(name, f5.Config.Interfaces)
}

func (f5 *F5) FindSelfIPVlan(vlan string) (string, *SelfIP) {
	return FindSelfIPVlan(vlan, f5.Config.SelfIPs)
}

//FSCKPut enables or disables FSCK
func (f5 *F5) FSCKPut(f *FSCK) error {
	if err := f5.GetDumpe2fs(); err != nil {
		return err
	}
	command := ""
	for _, filesystem := range f5.Management.Dumpe2fsInfo.Info {
		if *f.Enabled == true {
			command += fmt.Sprintf("tune2fs -c %s -i %sw %s; ", strconv.Itoa(rand.Intn(35-25)+25), strconv.Itoa(rand.Intn(30-20)+20), filesystem.Device)
		} else {
			command += fmt.Sprintf("tune2fs -c 0 -i 0 %s; ", filesystem.Device)
		}
	}
	_, err := f5.sshClient.PostCommand(fmt.Sprintf("run util bash -c '%s'", command), defaultPrompt, 10)
	if err != nil {
		return err
	}
	if err := f5.GetFSCK(); err != nil {
		return err
	}
	if *f5.Management.FSCK.Enabled != *f.Enabled {
		return errors.New("unable to set FSCK configuration")
	}
	return nil
}

func (f5 *F5) ModifyTrustDomain(action string, d *ShCmDevice, passwd string) (error, int) {
	var cmd string
	switch action {
	case "add":
		cmd = fmt.Sprintf(
			"modify /cm trust-domain /Common/Root ca-devices add { %s } name %s username admin password %s",
			*d.ConfigSyncIp, *d.Hostname, passwd,
		)
	case "delete":
		cmd = fmt.Sprintf("modify /cm trust-domain /Common/Root ca-devices delete { %s }", *d.Hostname)
	default:
		return errors.New("action must be add or delete"), http.StatusBadRequest
	}

	res, err := f5.sshClient.PostCommand(cmd, defaultPrompt, 45)
	if err != nil {
		return err, http.StatusInternalServerError
	}
	if err, code := f5RespHandler(*res.Response); err != nil {
		return err, code
	}
	return nil, http.StatusOK
}

func (f5 *F5) IsActive() (bool, error) {
	k, err := f5.GetDBKey("failover.state")
	if err != nil {
		return false, err
	}
	if k.Value == "active" {
		return true, nil
	}

	return false, nil
}

func (f5 *F5) MajorVersion() (string, error) {
	if f5.Status.Version == nil {
		if err := f5.Version(15); err != nil {
			return "", err
		}
	}
	if f5.Status.Version.Version == "" {
		return "", errors.New("unable to determine F5 version")
	}
	return f5.Status.Version.Version[0:2], nil
}

func (f5 *F5) SoftwareInstallPut(si SoftwareInstall) error {
	res, err := f5.sshClient.PostCommand(fmt.Sprintf("install sys software %s %s volume %s", si.FileType, si.Image, si.Volume), defaultPrompt, 10)
	if err != nil {
		return err
	}
	if f5Resp := cmdResponseRegex.FindStringSubmatch(*res.Response); f5Resp != nil {
		return errors.New(f5Resp[1])
	}

	return nil
}

func (f5 *F5) PasswordPolicyPut(p *PasswordPolicy) error {
	if p == nil {
		return nil
	}
	var cmd []*sshAPI.Command
	if *f5.Config.PasswordPolicy.PolicyEnforcement != *p.PolicyEnforcement {
		pe := "modify auth password-policy policy-enforcement disabled"
		if *p.PolicyEnforcement {
			pe = "modify auth password-policy policy-enforcement enabled"
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(pe, 10, promptRegex))
	}
	if f5.Config.PasswordPolicy.MinimumLength != p.MinimumLength {
		ml := "modify auth password-policy minimum-length 6"
		if p.MinimumLength != 0 {
			ml = fmt.Sprintf("modify auth password-policy minimum-length %v", p.MinimumLength)
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(ml, 10, promptRegex))
	}
	if f5.Config.PasswordPolicy.ExpireWarning != p.ExpireWarning {
		ew := "modify auth password-policy expiration-warning 7"
		if p.ExpireWarning != 0 {
			ew = fmt.Sprintf("modify auth password-policy expiration-warning %v", p.MinimumLength)
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(ew, 10, promptRegex))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy description \"%s\"", p.Description), 10, promptRegex))
	cmd = append(cmd, sshAPI.ReCommandCreate(
		fmt.Sprintf("modify auth password-policy "+
			"max-login-failures %v "+
			"min-duration %v "+
			"required-lowercase %v "+
			"required-numeric %v "+
			"required-special %v "+
			"required-uppercase %v",
			p.MaxLoginFailures,
			p.MinDuration,
			p.RequiredLowercase,
			p.RequiredNumeric,
			p.RequiredSpecial,
			p.RequiredUppercase),
		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)
	return f5.PasswordPolicy(15)
}

func (f5 *F5) PasswordPolicyPost(p *PasswordPolicy) error {
	if p == nil {
		return nil
	}
	var cmd []*sshAPI.Command
	if p.PolicyEnforcement != nil {
		if *p.PolicyEnforcement {
			cmd = append(cmd, sshAPI.ReCommandCreate("modify auth password-policy policy-enforcement enabled", 10, promptRegex))
		} else {
			cmd = append(cmd, sshAPI.ReCommandCreate("modify auth password-policy policy-enforcement disabled", 10, promptRegex))
		}
	}

	if p.MinimumLength != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy minimum-length %v", p.MinimumLength), 10, promptRegex))
	}
	if p.ExpireWarning != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy expiration-warning %v", p.MinimumLength), 10, promptRegex))
	}
	if p.Description != "" {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy description \"%s\"", p.Description), 10, promptRegex))
	}
	if p.MaxLoginFailures != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy max-login-failures %v", p.MaxLoginFailures), 10, promptRegex))
	}
	if p.MinDuration != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy min-duration %v", p.MinDuration), 10, promptRegex))
	}
	if p.RequiredLowercase != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy required-lowercase %v", p.RequiredLowercase), 10, promptRegex))
	}
	if p.RequiredNumeric != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy required-numeric %v", p.RequiredNumeric), 10, promptRegex))
	}
	if p.RequiredSpecial != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy required-special %v", p.RequiredSpecial), 10, promptRegex))
	}
	if p.RequiredUppercase != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify auth password-policy required-uppercase %v", p.RequiredUppercase), 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)
	return f5.PasswordPolicy(15)
}

func (f5 *F5) PacketFilterAdd(p map[string]*PacketFilter) error {
	if len(p) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	for _, h := range p {
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("create net packet-filter %s %s %d", h.Name, h.Action, h.Order), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	var s []*PacketFilter
	f5.Config.PacketFilters = s
	return f5.PacketFilterConfig("", 25)
}

func (f5 *F5) PacketFilterDelete(p map[string]*PacketFilter) error {
	if len(p) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	for _, h := range p {
		cmd = append(cmd, sshAPI.ReCommandCreate(
			fmt.Sprintf("create net packet-filter %s %s %d", h.Name, h.Action, h.Order), 10, promptRegex))
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	var s []*PacketFilter
	f5.Config.PacketFilters = s
	return f5.PacketFilterConfig("", 25)
}

func (f5 *F5) PacketFilterTrustedAddAddress(netPrefixMap map[string]int) error {
	netStr := " "
	for net, pre := range netPrefixMap {
		netStr += fmt.Sprintf("%v/%v ", net, pre)
	}
	if netStr == " " {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify net packet-filter-trusted ip-addresses add {%v}", netStr), 10, promptRegex))
	res, err := f5.sshClient.PostCommands(cmd)
	f5.AppendCommands(res)
	if err != nil {
		return err
	}
	return nil
}

func (f5 *F5) PacketFilterTrustedDeleteAddress(netPrefixMap map[string]int) error {
	netStr := " "
	for net, pre := range netPrefixMap {
		netStr += fmt.Sprintf("%v/%v ", net, pre)
	}
	if netStr == " " {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("modify net packet-filter-trusted ip-addresses delete {%v}", netStr), 10, promptRegex))
	res, err := f5.sshClient.PostCommands(cmd)
	f5.AppendCommands(res)
	if err != nil {
		return err
	}
	return nil
}

func (f5 *F5) VlanInterface(vlan string) *string {
	for _, v := range f5.Config.VLANs {
		if strings.Contains(vlan, v.Name) {
			return v.Interfaces()
		}
	}
	return nil
}

func (f5 *F5) RedundancyState() (string, error) {
	key, err := f5.SysDB("failover.forceactive")
	if err != nil {
		return "", err
	}
	act := key.FindDBKey("failover.forceactive")
	if act.Value == "enable" {
		return "active", nil
	}
	key, err = f5.SysDB("failover.forcestandby")
	if err != nil {
		return "", err
	}
	act = key.FindDBKey("failover.forcestandby")
	if act.Value == "enable" {
		return "standby", nil
	}
	return "", nil

}

func (f5 *F5) RunConfigSync(group string) (error, bool) {
	ver, err := f5.MajorVersion()
	if err != nil {
		return err, false
	}
	var cmd []*sshAPI.Command
	if strings.Contains(ver, "10") {
		cmd = append(cmd, sshAPI.ReCommandCreate("run sys config-sync", 60, promptRegex))
	} else {
		if group == "" {
			cmd = append(cmd, sshAPI.ReCommandCreate("run util bash", 60, bashPromptRegex))
			cmd = append(cmd, sshAPI.ReCommandCreate("tmsh run cm config-sync to-group `tmsh list cm device-group one-line | grep 'sync-failover' | awk '{print $3}'`", 60, bashPromptRegex))
			cmd = append(cmd, sshAPI.ReCommandCreate("exit", 60, promptRegex))
		} else {
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("run /cm config-sync to-group %s", group), 60, promptRegex))
		}
	}
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err, false
	}
	for _, c := range res.Commands {
		if strings.Contains(strings.ToLower(*c.Response), "error") {
			return fmt.Errorf("%v", regexp.MustCompile(`(.|\n)*\n`).FindString(*c.Response)), true
		}
	}
	f5.AppendCommands(res)
	if !strings.Contains(ver, "10") {
		// Sleep command is needed to allow the F5 to complete a config-sync before
		// returning the config-sync state.
		time.Sleep(10 * time.Second)
	}
	return f5.GetSyncState(25), true
}

func (f5 *F5) SetTacLogDestination(destination, sharedSecret, fwdType string) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate(
		fmt.Sprintf("modify sys db config.auditing.forward.destination value %s", destination), 10, promptRegex))
	cmd = append(cmd, sshAPI.ReCommandCreate(
		fmt.Sprintf("modify sys db config.auditing.forward.sharedsecret value %s", sharedSecret), 10, promptRegex))
	cmd = append(cmd, sshAPI.ReCommandCreate(
		fmt.Sprintf("modify sys db config.auditing.forward.type value %s", fwdType), 10, promptRegex))
	res, err := f5.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	f5.AppendCommands(res)
	return nil
}

func (f5 *F5) getAddressListbyName(n string) *AddressList {
	for _, a := range f5.Config.AFMAddressLists {
		if a.Name == n {
			return a
		}
	}
	return nil
}
