package ruleset_v2

import (
	"bytes"
	"encoding/json"
	"fmt"

	"gopkg.in/yaml.v3"
	"ladder/proxychain"
)

type Rule struct {
	Domains               []string
	RequestModifications  []proxychain.RequestModification
	_rqms                 []_rqm // internal represenation of RequestModifications
	ResponseModifications []proxychain.ResponseModification
	_rsms                 []_rsm // internal represenation of ResponseModifications
}

// internal represenation of ResponseModifications
type _rsm struct {
	Name   string   `json:"name" yaml:"name"`
	Params []string `json:"params" yaml:"params"`
}

// internal represenation of RequestModifications
type _rqm struct {
	Name   string   `json:"name" yaml:"name"`
	Params []string `json:"params" yaml:"params"`
}

// implement type encoding/json/Marshaler
func (rule *Rule) UnmarshalJSON(data []byte) error {
	type Aux struct {
		Domains               []string `json:"domains"`
		RequestModifications  []_rqm   `json:"requestmodifications"`
		ResponseModifications []_rsm   `json:"responsemodifications"`
	}

	aux := &Aux{}
	if err := json.Unmarshal(data, aux); err != nil {
		return err
	}

	rule.Domains = aux.Domains
	rule._rqms = aux.RequestModifications
	rule._rsms = aux.ResponseModifications

	// convert requestModification function call string into actual functional option
	for _, rqm := range aux.RequestModifications {
		f, exists := rqmModMap[rqm.Name]
		if !exists {
			return fmt.Errorf("Rule::UnmarshalJSON => requestModifier '%s' does not exist, please check spelling", rqm.Name)
		}
		rule.RequestModifications = append(rule.RequestModifications, f(rqm.Params...))
	}

	// convert responseModification function call string into actual functional option
	for _, rsm := range aux.ResponseModifications {
		f, exists := rsmModMap[rsm.Name]
		if !exists {
			return fmt.Errorf("Rule::UnmarshalJSON => responseModifier '%s' does not exist, please check spelling", rsm.Name)
		}
		rule.ResponseModifications = append(rule.ResponseModifications, f(rsm.Params...))
	}

	return nil
}

func (rule *Rule) MarshalJSON() ([]byte, error) {
	aux := struct {
		Domains               []string `json:"domains"`
		RequestModifications  []_rqm   `json:"requestmodifications"`
		ResponseModifications []_rsm   `json:"responsemodifications"`
	}{
		Domains:               rule.Domains,
		RequestModifications:  rule._rqms,
		ResponseModifications: rule._rsms,
	}

	return json.MarshalIndent(aux, "", "    ")
}

// ============================================================
// YAML

// implement type yaml marshaller
func (rule *Rule) UnmarshalYAML(unmarshal func(interface{}) error) error {
	type Aux struct {
		Domains               []string `yaml:"domains"`
		RequestModifications  []_rqm   `yaml:"requestmodifications"`
		ResponseModifications []_rsm   `yaml:"responsemodifications"`
	}

	var aux Aux
	if err := unmarshal(&aux); err != nil {
		return err
	}

	rule.Domains = aux.Domains
	rule._rqms = aux.RequestModifications
	rule._rsms = aux.ResponseModifications

	// convert requestModification function call string into actual functional option
	for _, rqm := range aux.RequestModifications {
		f, exists := rqmModMap[rqm.Name]
		if !exists {
			return fmt.Errorf("Rule::UnmarshalYAML => requestModifier '%s' does not exist, please check spelling", rqm.Name)
		}
		rule.RequestModifications = append(rule.RequestModifications, f(rqm.Params...))
	}

	// convert responseModification function call string into actual functional option
	for _, rsm := range aux.ResponseModifications {
		f, exists := rsmModMap[rsm.Name]
		if !exists {
			return fmt.Errorf("Rule::UnmarshalYAML => responseModifier '%s' does not exist, please check spelling", rsm.Name)
		}
		rule.ResponseModifications = append(rule.ResponseModifications, f(rsm.Params...))
	}

	return nil
}

func (rule *Rule) MarshalYAML() (interface{}, error) {
	type Aux struct {
		Domains               []string `yaml:"domains"`
		RequestModifications  []_rqm   `yaml:"requestmodifications"`
		ResponseModifications []_rsm   `yaml:"responsemodifications"`
	}

	aux := &Aux{
		Domains:               rule.Domains,
		RequestModifications:  rule._rqms,
		ResponseModifications: rule._rsms,
	}

	var b bytes.Buffer
	y := yaml.NewEncoder(&b)
	y.SetIndent(2)
	err := y.Encode(aux)
	return b.String(), err
}
