package rules

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"iwarma.ru/console/correlator/events"
	"net/http"
	"strconv"
	"text/template"

	"iwarma.ru/console/correlator/config"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

const (
	AssetActionType = "asset"
)

type AssetAction struct {
	Name            string
	Description     string
	Manufacturer    string
	Model           string
	Ip              string
	Os              string
	Ports           string
	Vulnerabilities []string
	Group           string
	AssetType       string
	Status          string

	templates struct {
		name        *template.Template
		description *template.Template
		model       *template.Template
		ip          *template.Template
		ports       *template.Template
	}

	client *http.Client
	token  string
}

func (action *AssetAction) GetType() string {
	return AssetActionType
}

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

	result["type"] = AssetActionType
	result["name"] = action.Name
	result["description"] = action.Description
	result["manufacturer"] = action.Manufacturer
	result["model"] = action.Model
	result["ip"] = action.Ip
	result["os"] = action.Os
	result["ports"] = action.Ports

	if len(action.Vulnerabilities) > 0 {
		vulnerabilities := make([]interface{}, 0)
		for _, cur := range action.Vulnerabilities {
			tmp, err := strconv.Atoi(cur)
			if err != nil {
				return nil, err
			}
			vulnerabilities = append(vulnerabilities, tmp)
		}
		result["vulnerabilities"] = vulnerabilities
	}

	result["group"] = action.Group
	result["asset_type"] = action.AssetType
	result["status"] = action.Status

	return result, nil
}

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

	return json.Marshal(data)
}

func (action *AssetAction) UnmarshalJSON(b []byte) error {
	var data interface{}
	err := json.Unmarshal(b, &data)
	if err != nil {
		return err
	}

	return action.ParseInterface(data)
}

func (action *AssetAction) 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 != AssetActionType {
		return fmt.Errorf("bad type, expect %v got %v", ExecActionType, t)
	}

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

	action.Ip, ok = m["ip"].(string)
	if !ok {
		return errors.New("no IP")
	}

	// Not required
	action.Description, _ = m["description"].(string)
	action.Manufacturer, _ = m["manufacturer"].(string)
	action.Model, _ = m["model"].(string)
	action.Os, _ = m["os"].(string)
	action.Ports, _ = m["ports"].(string)
	action.Group, _ = m["group"].(string)
	action.AssetType, _ = m["asset_type"].(string)
	action.Status, _ = m["status"].(string)

	// For array
	if w, ok := m["vulnerabilities"]; ok {
		switch v := w.(type) {
		case []interface{}:
			{
				for _, cur := range v {
					switch w := cur.(type) {
					case string:
						action.Vulnerabilities = append(action.Vulnerabilities, w)
					case float64:
						action.Vulnerabilities = append(action.Vulnerabilities, fmt.Sprintf("%v", w))
					default:
						return fmt.Errorf("bad vulnerabilities type: %T with value %v in interface %v", cur, cur, m)
					}
				}
			}
		case string:
			action.Vulnerabilities = append(action.Vulnerabilities, v)
		case float64:
			action.Vulnerabilities = append(action.Vulnerabilities, fmt.Sprintf("%v", v))
		default:
			return fmt.Errorf("bad vulnerabilities type: %T with value %v in interface %v", v, v, m)
		}
	}

	return nil
}

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

	if events == nil || len(*events) == 0 {
		cl.Error("No events")
		return nil
	}

	var err error

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

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

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

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

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

	// Check if we need to prepare http client
	if action.client == nil {
		if viper.GetBool(config.ConsoleIgnoreSSLErrors) {
			transport := &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			}
			action.client = &http.Client{Transport: transport}
		} else {
			action.client = &http.Client{}
		}
	}

	// Get auth token
	if action.token == "" {
		action.token, err = ObtainAuthToken()
		if err != nil {
			cl.Errorf("Can't get auth token: %v", err)
			return err
		}
	}

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

		// Prepare body
		interfaceBody, err := action.ToInterface()
		if err != nil {
			return err
		}

		// Render templates
		if _, ok := interfaceBody["name"]; ok {
			interfaceBody["name"], err = renderTemplate(action.templates.name, event)
			if err != nil {
				return err
			}
		}

		if _, ok := interfaceBody["description"]; ok {
			interfaceBody["description"], err = renderTemplate(action.templates.description, event)
			if err != nil {
				return err
			}
		}

		if _, ok := interfaceBody["ip"]; ok {
			interfaceBody["ip"], err = renderTemplate(action.templates.ip, event)
			if err != nil {
				return err
			}
		}

		if _, ok := interfaceBody["model"]; ok {
			interfaceBody["model"], err = renderTemplate(action.templates.model, event)
			if err != nil {
				return err
			}
		}

		if _, ok := interfaceBody["ports"]; ok {
			interfaceBody["ports"], err = renderTemplate(action.templates.ports, event)
			if err != nil {
				return err
			}
		}

		// Add type to connect asset with sensor
		interfaceBody["sensor"] = event.GetString("type")

		// Send request
		jsonBody, err := json.Marshal(interfaceBody)
		if err != nil {
			cl.Errorf("Can't serialize body: %v", err)
			return err
		}

		cl.Debugf("Sending requset: %v", string(jsonBody))

		request, err := http.NewRequest("POST", viper.GetString(config.ConsoleUrlAsset), bytes.NewBuffer(jsonBody))
		if err != nil {
			cl.Errorf("Can't create request: %v", err)
			return err
		}

		// Set headers
		request.Header.Set("Authorization", fmt.Sprintf("Token %v", action.token))
		request.Header.Set("Content-type", "application/json")

		// Do request
		response, err := action.client.Do(request)
		if err != nil {
			cl.Errorf("Can't send request: %v", err)
			return err
		}

		// Check result
		body, err := ioutil.ReadAll(response.Body)
		if err != nil {
			cl.Errorf("Can't read response body: %v", err)
			return err
		}
		err = response.Body.Close()
		if err != nil {
			cl.Warningf("%v", err)
		}

		cl.Debugf("Got response: %v", string(body))

		if response.StatusCode != http.StatusCreated {
			cl.Errorf("Bad status code: %v", response.Status)
			return fmt.Errorf("bad server response code: %v", response.Status)
		}
	}

	return nil

}
