package rules

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"iwarma.ru/console/correlator/events"
	"log/syslog"
	"strconv"
	"text/template"

	"github.com/spf13/viper"
	"iwarma.ru/console/correlator/config"

	log "github.com/sirupsen/logrus"
)

const (
	SyslogProtoTcp = "tcp"
	SyslogProtoUdp = "udp"

	SyslogActionType = "syslog"
)

// SyslogAction Send record with syslog to selected host
type SyslogAction struct {
	Host         string
	Port         int
	Proto        string
	Name         string
	Template     string
	writer       *syslog.Writer
	bodyTemplate *template.Template
}

func (action SyslogAction) GetType() string {
	return SyslogActionType
}

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

	if events == nil {
		contextLogger.Error("Nil events provided")
		return errors.New("nil events provided")
	}

	// Create connection to syslog server
	if action.writer == nil {
		if action.Proto != SyslogProtoTcp && action.Proto != SyslogProtoUdp {
			return fmt.Errorf("bad syslog protocol %v", action.Proto)
		}
		writer, err := syslog.Dial(action.Proto, fmt.Sprintf("%v:%v", action.Host, action.Port), syslog.LOG_LOCAL0, viper.GetString(config.SyslogTag))
		if err != nil {
			return err
		}

		action.writer = writer
	}

	// Create template
	if action.bodyTemplate == nil {
		bodyTemplate, err := template.New("Action").Parse(action.Template)
		if err != nil {
			return err
		}

		action.bodyTemplate = bodyTemplate
	}

	// Send messages
	for _, cur := range *events {
		var buf bytes.Buffer
		err := action.bodyTemplate.Execute(&buf, *cur)
		if err != nil {
			return err
		}

		err = action.writer.Info(buf.String())
		if err != nil {
			return err
		}
	}
	return nil
}

func (action SyslogAction) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		CurType  string `json:"type"`
		Host     string `json:"host"`
		Port     string `json:"port"`
		Proto    string `json:"protocol"`
		Name     string `json:"name"`
		Format   string `json:"format"`
		Template string `json:"template"`
	}{
		CurType:  SyslogActionType,
		Host:     action.Host,
		Port:     strconv.Itoa(action.Port),
		Proto:    action.Proto,
		Name:     action.Name,
		Format:   "",
		Template: action.Template,
	})
}

func (action *SyslogAction) UnmarshalJSON(b []byte) error {
	cur := struct {
		CurType  string `json:"type"`
		Host     string `json:"host"`
		Port     string `json:"port"`
		Proto    string `json:"protocol"`
		Name     string `json:"name"`
		Format   string `json:"format"`
		Template string `json:"template"`
	}{}

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

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

	action.Host = cur.Host
	action.Port, err = strconv.Atoi(cur.Port)
	if err != nil {
		return fmt.Errorf("can't parse port: %v", cur.Port)
	}
	action.Proto = cur.Proto
	action.Name = cur.Name
	action.Template = cur.Template

	return nil
}

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

	action.Host, ok = m["host"].(string)
	if !ok {
		return errors.New("can't get host")
	}

	// Port can bee int (float64) or string
	if port, ok := m["port"].(float64); ok {
		action.Port = int(port)
	} else if port, ok := m["port"].(string); ok {
		cur, err := strconv.Atoi(port)
		if err != nil {
			return fmt.Errorf("bad port string: %v", port)
		}
		action.Port = cur
	} else {
		return fmt.Errorf("bad port type: %T with value %v", port, port)
	}

	action.Proto, ok = m["protocol"].(string)
	if !ok {
		return errors.New("can't get protocol")
	}

	action.Name, ok = m["name"].(string)
	if !ok {
		return errors.New("can't get name")
	}

	// Template can be in data or template
	if action.Template, ok = m["data"].(string); !ok {
		if action.Template, ok = m["template"].(string); !ok {
			return errors.New("can't get template")
		}
	}

	return nil
}
