package openiscsi

import (
	"bytes"
	"path"
	"regexp"
	"strings"
	"text/template"

	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
	boshsys "github.com/cloudfoundry/bosh-utils/system"
)

type concreteOpenIscsiAdmin struct {
	fs     boshsys.FileSystem
	runner boshsys.CmdRunner
	logger boshlog.Logger
	logtag string
}

func NewConcreteOpenIscsiAdmin(fs boshsys.FileSystem, runner boshsys.CmdRunner, logger boshlog.Logger) OpenIscsi {
	return concreteOpenIscsiAdmin{
		fs:     fs,
		runner: runner,
		logger: logger,
		logtag: "OpenIscsiAdmin",
	}
}

func (iscsi concreteOpenIscsiAdmin) Setup(iqn, username, password string) error {
	iscsi.logger.Info(iscsi.logtag, "Setup Open-iscsi, initializing /etc/iscsi/initiatorname.iscsi,iscsid.conf")
	buffer := bytes.NewBuffer([]byte{})
	t := template.Must(template.New("Open-iscsi-initiator").Parse(initiatorNameIscsiTemplate))

	type initiatorNameArgs struct {
		Iqn string
	}

	err := t.Execute(buffer, initiatorNameArgs{iqn})
	if err != nil {
		return bosherr.WrapError(err, "Generating initiatorname.iscsi of Open-iscsi")
	}

	err = iscsi.fs.WriteFile(path.Join("/etc/iscsi", "initiatorname.iscsi"), buffer.Bytes())
	if err != nil {
		return bosherr.WrapError(err, "Writing to /etc/iscsi/initiatorname.iscsi")
	}

	buffer.Reset()

	type iscsidConfArgs struct {
		Username string
		Password string
	}

	t = template.Must(template.New("Open-iscsi-conf").Parse(iscsidConfTemplate))
	err = t.Execute(buffer, iscsidConfArgs{username, password})
	if err != nil {
		return bosherr.WrapError(err, "Generating iscsid.conf of Open-iscsi")
	}

	err = iscsi.fs.WriteFile(path.Join("/etc/iscsi", "iscsid.conf"), buffer.Bytes())
	if err != nil {
		return bosherr.WrapError(err, "Writing to /etc/iscsi/iscsid.conf")
	}

	buffer.Reset()

	err = iscsi.Restart()
	if err != nil {
		return bosherr.WrapError(err, "Restarting iscsi after modifying the /etc/iscsi/iscsid.conf file")
	}

	_, _, _, err = iscsi.runner.RunCommand("/etc/init.d/multipath-tools", "restart")
	if err != nil {
		return bosherr.WrapError(err, "Restarting multipath after restarting open-iscsi")
	}

	return nil
}

// Open-iscsi initiator file - /etc/iscsi/initiatorname.iscsi
const initiatorNameIscsiTemplate = `InitiatorName={{ .Iqn }}`

// Open-iscsi daemon configuration file - /etc/iscsi/iscsid.conf
const iscsidConfTemplate = `# Generated by bosh-agent
node.startup = automatic
node.session.auth.authmethod = CHAP
node.session.auth.username = {{.Username}}
node.session.auth.password = {{.Password}}
discovery.sendtargets.auth.authmethod = CHAP
discovery.sendtargets.auth.username = {{.Username}}
discovery.sendtargets.auth.password = {{.Password}}
node.session.timeo.replacement_timeout = 120
node.conn[0].timeo.login_timeout = 15
node.conn[0].timeo.logout_timeout = 15
node.conn[0].timeo.noop_out_interval = 10
node.conn[0].timeo.noop_out_timeout = 15
node.session.iscsi.InitialR2T = No
node.session.iscsi.ImmediateData = Yes
node.session.iscsi.FirstBurstLength = 262144
node.session.iscsi.MaxBurstLength = 16776192
node.conn[0].iscsi.MaxRecvDataSegmentLength = 65536
`

func (iscsi concreteOpenIscsiAdmin) Start() (err error) {
	iscsi.logger.Info(iscsi.logtag, "Start Open-iscsi deamon")
	_, _, _, err = iscsi.runner.RunCommand("/etc/init.d/open-iscsi", "start")
	return
}

func (iscsi concreteOpenIscsiAdmin) Stop() (err error) {
	iscsi.logger.Info(iscsi.logtag, "Stop Open-iscsi deamon")
	_, _, _, err = iscsi.runner.RunCommand("/etc/init.d/open-iscsi", "stop")
	return
}

func (iscsi concreteOpenIscsiAdmin) Restart() (err error) {
	iscsi.logger.Info(iscsi.logtag, "Restart Open-iscsi deamon")
	_, _, _, err = iscsi.runner.RunCommand("/etc/init.d/open-iscsi", "restart")
	return
}

func (iscsi concreteOpenIscsiAdmin) Discovery(ipAddress string) (err error) {
	iscsi.logger.Info(iscsi.logtag, "Discovering lun against %s", ipAddress)
	_, _, _, err = iscsi.runner.RunCommand(
		"iscsiadm",
		"-m",
		"discovery",
		"-t",
		"sendtargets",
		"-p",
		ipAddress,
	)
	return
}

func (iscsi concreteOpenIscsiAdmin) Login() (err error) {
	iscsi.logger.Info(iscsi.logtag, "Iscsiadm session login")
	_, _, _, err = iscsi.runner.RunCommand(
		"iscsiadm",
		"-m",
		"node",
		"-l",
	)
	return
}

func (iscsi concreteOpenIscsiAdmin) IsLoggedin() (bool, error) {
	stdout, stderr, _, err := iscsi.runner.RunCommand(
		"iscsiadm",
		"-m",
		"session",
	)
	if err != nil {
		if strings.Contains(stderr, "No active sessions.") {
			return false, nil
		}
		return false, bosherr.WrapError(err, "Checking all current sessions logged in")
	}

	r := regexp.MustCompile(`^tcp: \[\d+\]`)
	if r.MatchString(stdout) {
		return true, nil
	}
	return false, nil
}

func (iscsi concreteOpenIscsiAdmin) Logout() (err error) {
	iscsi.logger.Info(iscsi.logtag, "Iscsiadm session logout")
	_, _, _, err = iscsi.runner.RunCommand(
		"iscsiadm",
		"-m",
		"node",
		"-u",
	)
	return
}
