package routeupdater

import (
	"encoding/json"
	"fmt"
	"os/exec"

	"github.com/filtr/infrastructure/go/openvpn-route-updater/internal/logger"

	"github.com/pkg/errors"
)

// CliInterface for allowing testing on mock objects
type CliInterface interface {
	SetRoute(group, routeID, routeValue string) error
	RemoveRoute(group, routeID string) error
	GetGroupProps(group string) (map[string]interface{}, error)
	//RestartServer() ([]byte, error)
}

// OpenVPNCli todo
type OpenVPNCli struct {
	log     logger.Logger
	CliPath string
}

// NewCli - initialize new OpenVPNCli object
func NewCli(log logger.Logger, CliPath string) *OpenVPNCli {
	cli := &OpenVPNCli{log: log, CliPath: CliPath}
	return cli
}

// SetRoute - run update route operation for given route
func (c OpenVPNCli) SetRoute(group, routeID, routeValue string) error {
	cmdString := fmt.Sprintf("%v --user \"%v\" --key %v --value %v UserPropPut", c.CliPath, group, routeID, routeValue)
	c.log.Debugf("Running command: %v", cmdString)
	response, err := exec.Command(c.CliPath, "--user", group, "--key", routeID, "--value", routeValue, "UserPropPut").Output()
	if err != nil {
		c.log.Warnf("Error while running command %v", cmdString)
		return errors.Wrapf(err, "Error while running command %v", cmdString)
	}

	c.log.Debug(string(response))

	// Todo - somehow parse that response instead of just comparing it to a valid one
	if string(response) != "[True, {}]\n" {
		c.log.Warnf("Command output is not valid, got %v", string(response))
		return errors.New(fmt.Sprintf("Command output is not valid, got %v", string(response)))
	}
	return nil
}

// RemoveRoute - run remove route operation for given route
func (c OpenVPNCli) RemoveRoute(group, routeID string) error {
	cmdString := fmt.Sprintf("%v --user \"%v\" --key %v UserPropDel", c.CliPath, group, routeID)
	c.log.Debugf("Running command: %v", cmdString)
	response, err := exec.Command(c.CliPath, "--user", group, "--key", routeID, "UserPropDel").Output()
	if err != nil {
		c.log.Warnf("Error while running command %v", cmdString)
		return errors.Wrapf(err, "Error while running command %v", cmdString)
	}

	c.log.Debug(string(response))

	// Todo - somehow parse that response instead of just comparing it to a valid one
	if string(response) != "[True, {}]\n" {
		c.log.Warnf("Command output is not valid, got %v", string(response))
		return errors.New(fmt.Sprintf("Command output is not valid, got %v", string(response)))
	}
	return nil
}

// GetGroupProps - get openvpn group properties as a json
func (c OpenVPNCli) GetGroupProps(group string) (map[string]interface{}, error) {
	cmdString := fmt.Sprintf("%v --pfilt \"%v\" UserPropGet", c.CliPath, group)
	c.log.Debugf("Running command: %v", cmdString)
	response, err := exec.Command(c.CliPath, "--pfilt", group, "UserPropGet").Output()
	if err != nil {
		c.log.Warnf("Error while running command %v", cmdString)
		return nil, err
	}
	c.log.Debug(string(response))

	var f interface{}
	err = json.Unmarshal(response, &f)
	if err != nil {
		return nil, err
	}
	m := f.(map[string]interface{})
	if _, exists := m[group]; exists {
		props := m[group].(map[string]interface{})
		return props, nil
	}
	return nil, errors.New(fmt.Sprintf("The group %v doesn't exist in OpenVPN", group))
}

// RestartServer - restart required services to apply changes
func (c OpenVPNCli) RestartServer() ([]byte, error) {
	cmdString := fmt.Sprintf("%v start", c.CliPath)
	c.log.Debugf("Running command: %v", cmdString)
	cmd := exec.Command(c.CliPath, "start")
	response, err := cmd.Output()
	if err != nil {
		c.log.Warnf("Error while running command %v", cmdString)
		return nil, err
	}

	return response, nil
}
