package rules

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

	"iwarma.ru/console/correlator/config"

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

const (
	FirewallActionType = "firewall"
)

type ArmaBool bool

func (b ArmaBool) ToInterface() interface{} {
	if b == true {
		return 1
	} else {
		return 0
	}
}

func (b *ArmaBool) ParseInterface(v interface{}) error {
	switch value := v.(type) {
	case int:
		if value >= 1 {
			*b = true
		} else {
			*b = false
		}
	case bool:
		*b = ArmaBool(value)
	case nil:
		return errors.New("no value")
	default:
		return fmt.Errorf("can't parse type: %T with value %v", value, value)
	}

	return nil
}

type FirewallAction struct {
	Enabled         ArmaBool
	Action          string
	Quick           ArmaBool
	Interface       string
	Direction       string
	IPProtocol      string
	Protocol        string
	SourceNet       string
	SourceNot       ArmaBool
	SourcePort      string
	DestinationNet  string
	DestinationNot  ArmaBool
	DestinationPort string
	Log             ArmaBool
	Description     string
	Gateway         string
	Sequence        string

	// Where to send request
	url struct {
		ip     string
		scheme string
		key    string
		secret string
	}

	templates struct {
		sourceNet       *template.Template
		sourcePort      *template.Template
		destinationNet  *template.Template
		destinationPort *template.Template
	}

	client *http.Client
}

const (
	responseSaved = "saved"
)

func (action *FirewallAction) GetType() string {
	return FirewallActionType
}

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

	result["type"] = FirewallActionType
	result["enabled"] = action.Enabled.ToInterface()
	result["action"] = action.Action
	result["quick"] = action.Quick.ToInterface()
	result["interface"] = action.Interface
	result["interface"] = fmt.Sprintf("[%v]", action.Interface)
	result["description"] = action.Description
	result["direction"] = action.Direction
	result["ipprotocol"] = action.IPProtocol
	result["protocol"] = action.Protocol
	result["source_net"] = action.SourceNet
	result["source_not"] = action.SourceNot.ToInterface()
	result["source_port"] = action.SourcePort
	result["destination_net"] = action.DestinationNet
	result["destination_port"] = action.DestinationPort
	result["destination_not"] = action.DestinationNot.ToInterface()
	result["log"] = action.Log.ToInterface()
	result["gateway"] = action.Gateway
	result["sequence"] = action.Sequence

	url := make(map[string]interface{})
	url["ip"] = action.url.ip
	url["key"] = action.url.key
	url["scheme"] = action.url.scheme
	url["secret"] = action.url.secret

	result["sensor"] = url

	return result, nil
}

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

	err := action.Enabled.ParseInterface(m["enabled"])
	if err != nil {
		return fmt.Errorf("can't parse \"enabled\" option: %v", err)
	}

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

	if _, ok = m["quick"]; ok {
		err = action.Quick.ParseInterface(m["quick"])
		if err != nil {
			return fmt.Errorf("can't parse \"quick\" option: %v", err)
		}
	}

	if value, ok := m["interface"]; ok {
		switch v := value.(type) {
		case string:
			action.Interface = v
		case []interface{}:
			arr := make([]string, len(v))
			for i := range v {
				arr[i] = fmt.Sprintf("%v", v[i])
			}

			action.Interface = strings.Join(arr, ",")
		case []string:
			action.Interface = strings.Join(v, ",")
		default:
			return fmt.Errorf("bad interface type: %T (%v)", v, v)
		}
	} else {
		return errors.New("no interface field")
	}

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

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

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

	action.SourceNet, ok = m["source_net"].(string)
	if !ok {
		return errors.New("no source net")
	}

	if _, ok = m["source_not"]; ok {
		err = action.SourceNot.ParseInterface(m["source_not"])
		if err != nil {
			return fmt.Errorf("can't parse \"source_not\" option: %v", err)
		}
	}

	action.DestinationNet, ok = m["destination_net"].(string)
	if !ok {
		return errors.New("no destination net")
	}

	if _, ok = m["destination_not"]; ok {
		err = action.DestinationNot.ParseInterface(m["destination_not"])
		if err != nil {
			return fmt.Errorf("can't parse \"destination_not\" option: %v", err)
		}
	}

	if _, ok = m["log"]; ok {
		err = action.Log.ParseInterface(m["log"])
		if err != nil {
			return fmt.Errorf("can't parse \"log\" option: %v", err)
		}
	}

	if _, ok = m["sequence"]; ok {
		switch v := m["sequence"].(type) {
		case string:
			action.Sequence = v
		case int:
			action.Sequence = fmt.Sprintf("%v", v)
		default:
			return fmt.Errorf("can't parse type %T with value of %v", m["sequence"], m["sequence"])
		}
	}

	if urlInterface, ok := m["sensor"]; ok {
		url, ok := urlInterface.(map[string]interface{})
		if !ok {
			return errors.New("can't convert sensor value")
		}

		action.url.ip, ok = url["ip"].(string)
		if !ok {
			return errors.New("no Firewall target IP set")
		}

		action.url.key, ok = url["key"].(string)
		if !ok {
			return errors.New("no Firewall target key set")
		}

		action.url.scheme, ok = url["scheme"].(string)
		if !ok {
			return errors.New("no Firewall target scheme set")
		}

		action.url.secret, ok = url["secret"].(string)
		if !ok {
			return errors.New("no Firewall target secret set")
		}
	} else {
		return errors.New("no sensor field")
	}

	// Not required
	action.Description, _ = m["description"].(string)
	action.SourcePort, _ = m["source_port"].(string)
	action.DestinationPort, _ = m["destination_port"].(string)
	action.Gateway, _ = m["gateway"].(string)

	return nil
}

// Create rule structure for ARMAIF
func (action FirewallAction) toRule() (map[string]interface{}, map[string]interface{}, error) {
	res := make(map[string]interface{})
	var err error

	res["enabled"] = action.Enabled.ToInterface()
	res["sequence"], err = strconv.Atoi(action.Sequence)
	if err != nil {
		return nil, nil, err
	}

	res["action"] = action.Action
	res["quick"] = action.Quick.ToInterface()
	res["interface"] = action.Interface
	res["direction"] = action.Direction
	res["ipprotocol"] = action.IPProtocol
	res["protocol"] = action.Protocol
	res["source_net"] = action.SourceNet
	res["source_port"] = action.SourcePort
	res["source_not"] = action.SourceNot.ToInterface()
	res["destination_net"] = action.DestinationNet
	res["destination_port"] = action.DestinationPort
	res["destination_not"] = action.DestinationNot.ToInterface()
	res["gateway"] = action.Gateway
	res["log"] = action.Log.ToInterface()
	res["description"] = action.Description

	rule := make(map[string]interface{})
	rule["rule"] = res

	return rule, res, nil
}

func (action *FirewallAction) ApplyRule() error {
	cl := log.WithFields(log.Fields{"type": FirewallActionType, "part": "ApplyRule"})

	request, err := http.NewRequest("POST", fmt.Sprintf("%v://%v/api/firewall/filter/apply/", action.url.scheme, action.url.ip), nil)
	if err != nil {
		cl.Errorf("Can't create request: %v", err)
		return err
	}

	// Set headers
	request.SetBasicAuth(action.url.key, action.url.secret)

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

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

	// Dump request and response
	if viper.GetBool(config.DebugDumpRequest) {
		// No need to wait
		go DumpNetwork("firewall", *request.URL, nil, body, response.StatusCode)
	}

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

	// Check response
	var resp map[string]interface{}
	err = json.Unmarshal(body, &resp)
	if err != nil {
		cl.Errorf("Can't unmarshall response: %v", err)
		return fmt.Errorf("can't unmarshall response: %v", err)
	}

	statusInt, ok := resp["status"]
	if !ok {
		cl.Errorf("No status field: %v", resp)
		return fmt.Errorf("no status field: %v", resp)
	}

	status, ok := statusInt.(string)
	if !ok {
		cl.Error("Can't convert status to string")
		return errors.New("can't convert status to string")
	}

	// Trim all \n and so on
	status = strings.TrimFunc(status, func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsNumber(r)
	})

	if status == "OK" {
		return nil
	}

	cl.Errorf("Bad status value: %v", status)
	return fmt.Errorf("bad status value: %v", status)
}

func (action *FirewallAction) Perform(events *[]*events.Event) error {
	cl := log.WithFields(log.Fields{"type": FirewallActionType, "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.sourceNet == nil {
		action.templates.sourceNet, err = template.New("SourceNet").Parse(action.SourceNet)
		if err != nil {
			cl.Errorf("Can't create source net template: %v", err)
			return err
		}

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

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

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

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

		// Prepare body
		ibody, data, err := action.toRule()
		if err != nil {
			return err
		}

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

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

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

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

		// Check if we have a http client
		if action.client == nil {
			if viper.GetBool(config.ActionFirewallRuleIgnoreSSLErrors) {
				transport := &http.Transport{
					TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
				}
				action.client = &http.Client{Transport: transport}
				cl.Debug("Disable SSL validation")
			} else {
				action.client = &http.Client{}
			}
		}

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

		request, err := http.NewRequest("POST", fmt.Sprintf("%v://%v/api/firewall/filter/addRule/", action.url.scheme, action.url.ip), bytes.NewBuffer(jsonBody))
		if err != nil {
			cl.Errorf("Can't create request: %v", err)
			return err
		}

		// Set headers
		request.SetBasicAuth(action.url.key, action.url.secret)
		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
		}
		defer response.Body.Close()

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

		// Dump request and response
		if viper.GetBool(config.DebugDumpRequest) {
			// No need to wait
			go DumpNetwork("firewall", *request.URL, ibody, body, response.StatusCode)
		}

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

		// Check response
		var resp map[string]interface{}
		err = json.Unmarshal(body, &resp)
		if err != nil {
			cl.Errorf("Can't unmarshall response: %v", err)
			return fmt.Errorf("can't unmarshall response: %v", err)
		}

		res, ok := resp["result"]
		if !ok {
			cl.Errorf("Bad ARMAIF response: %v", string(body))
			return fmt.Errorf("bad ARMAIF response: %v", string(body))
		}

		resVal, ok := res.(string)
		if !ok {
			cl.Errorf("Bad ARMAIF response type: %T - %v", resVal, resVal)
			return fmt.Errorf("bad ARMAIF response type: %T - %v", resVal, resVal)
		}

		if resVal != responseSaved {
			cl.Errorf(`Bad armaif response. Expect "%v", got ""%v"`, responseSaved, resVal)
			cl.Debugf(`Got response: %v`, string(body))
			return fmt.Errorf("bad armaif response. Expect \"%v\", got \"%v\"", responseSaved, resVal)
		}
	}

	return action.ApplyRule()
}

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

	return json.Marshal(data)
}

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

	return action.ParseInterface(data)
}
