package rules

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"iwarma.ru/console/correlator/events"
	"os/exec"
	"strings"
	"text/template"

	log "github.com/sirupsen/logrus"
)

const (
	ExecActionType = "exec"
)

type ExecAction struct {
	Path string
	Args string
	Env  string
	Cwd  string

	templates struct {
		path *template.Template
		args *template.Template
		env  *template.Template
		cwd  *template.Template
	}
}

func (action *ExecAction) GetType() string {
	return ExecActionType
}

func (action ExecAction) ToInterface() (map[string]interface{}, error) {
	result := make(map[string]interface{})

	result["type"] = ExecActionType
	result["path"] = action.Path
	result["args"] = action.Args
	result["env"] = action.Env
	result["cwd"] = action.Cwd

	return result, nil
}

func (action ExecAction) MarshalJSON() ([]byte, error) {
	data, err := action.ToInterface()
	if err != nil {
		return nil, err
	}

	return json.Marshal(data)
}

func (action *ExecAction) UnmarshalJSON(b []byte) error {
	cur := struct {
		CurType string `json:"type"`
		Path    string `json:"path"`
		Args    string `json:"args"`
		Env     string `json:"env"`
		Cwd     string `json:"cwd"`
	}{}

	err := json.Unmarshal(b, &cur)
	if err != nil {
		return err
	}

	if cur.CurType != ExecActionType {
		return fmt.Errorf("bad action type, Expect %v, got %v", ExecActionType, cur.CurType)
	}

	action.Path = cur.Path
	action.Args = cur.Args
	action.Env = cur.Env
	action.Cwd = cur.Cwd

	return nil
}

func (action *ExecAction) ParseInterface(v interface{}) error {
	m, ok := v.(map[string]interface{})
	if !ok {
		return fmt.Errorf("can't parse %v from %T", v, v)
	}

	t, ok := m["type"].(string)
	if !ok {
		return errors.New("no type")
	}

	if t != ExecActionType {
		return fmt.Errorf("bad type, expect %v got %v", ExecActionType, t)
	}

	path, ok := m["path"].(string)
	if !ok {
		return errors.New("no path")
	}
	action.Path = path

	// Args isn't not required
	args, ok := m["args"].(string)
	if ok {
		action.Args = args
	}

	// Env isn't required
	env, ok := m["env"].(string)
	if ok {
		action.Env = env
	}

	// Cwd isn't required
	cwd, ok := m["cwd"].(string)
	if ok {
		action.Cwd = cwd
	}

	return nil
}

func (action *ExecAction) Perform(events *[]*events.Event) error {
	cl := log.WithFields(log.Fields{"type": ExecActionType, "event_count": len(*events)})
	cl.Debug("Start action")
	defer cl.Debug("End action")

	var err error

	// Check if we need to prepare templates
	if action.templates.path == nil {
		action.templates.path, err = template.New("Path").Parse(action.Path)
		if err != nil {
			cl.Errorf("Can't create path template: %v", err)
			return err
		}

		action.templates.args, err = template.New("Args").Parse(action.Args)
		if err != nil {
			cl.Errorf("Can't create args template: %v", err)
			return err
		}

		action.templates.env, err = template.New("Env").Parse(action.Env)
		if err != nil {
			cl.Errorf("Can't create env template: %v", err)
			return err
		}

		action.templates.cwd, err = template.New("Cwd").Parse(action.Cwd)
		if err != nil {
			cl.Errorf("Can't create cwd template: %v", err)
			return err
		}
	}

	// Actual action
	for _, event := range *events {

		// Render templates
		path, err := renderTemplate(action.templates.path, event)
		if err != nil {
			cl.Errorf("Can't render path: %v", err)
			return err
		}

		args, err := renderTemplate(action.templates.args, event)
		if err != nil {
			cl.Errorf("Can't render args: %v", err)
			return err
		}

		env, err := renderTemplate(action.templates.env, event)
		if err != nil {
			cl.Errorf("Can't render env: %v", err)
			return err
		}

		cwd, err := renderTemplate(action.templates.cwd, event)
		if err != nil {
			cl.Errorf("Can't render cwd: %v", err)
			return err
		}

		// Prepare command
		var buf bytes.Buffer

		fullArgs := make([]string, 1)
		fullArgs[0] = path
		for _, cur := range strings.Split(args, " ") {
			fullArgs = append(fullArgs, cur)
		}

		cmd := &exec.Cmd{
			Path:   path,
			Args:   fullArgs,
			Env:    strings.Split(env, " "),
			Dir:    cwd,
			Stdout: &buf,
			Stderr: &buf,
		}

		debLog := log.WithFields(log.Fields{
			"type": ExecActionType, "event": event.GetString("event_id"),
			"path": cmd.Path, "args": cmd.Args,
			"env": cmd.Env, "cwd": cmd.Dir,
		})

		debLog.Debugf("Starting command")

		// TODO: Maybe set to goroutine
		err = cmd.Start()
		if err != nil {
			cl.Errorf("Script execution error: %v", err)
			debLog.Debugf("Executabe output:\n%v", buf.String())
			return err
		}

		err = cmd.Wait()
		if err != nil {
			cl.Errorf("Error while waiting script finish: %v", err)
			debLog.Debugf("Executabe output:\n%v", buf.String())
			return err
		}

		debLog.Debugf("Executabe output:\n%v", buf.String())
	}

	return nil
}
