package option

import (
	C "github.com/sagernet/sing-box/constant"
	E "github.com/sagernet/sing/common/exceptions"
	"github.com/sagernet/sing/common/json"
)

type _RuleAction struct {
	Action         string              `json:"action,omitempty"`
	RouteOptions   RouteActionOptions  `json:"-"`
	RejectOptions  RejectActionOptions `json:"-"`
	SniffOptions   RouteActionSniff    `json:"-"`
	ResolveOptions RouteActionResolve  `json:"-"`
}

type RuleAction _RuleAction

func (r RuleAction) MarshalJSON() ([]byte, error) {
	var v any
	switch r.Action {
	case C.RuleActionTypeRoute:
		r.Action = ""
		v = r.RouteOptions
	case C.RuleActionTypeReturn:
		v = nil
	case C.RuleActionTypeReject:
		v = r.RejectOptions
	case C.RuleActionTypeHijackDNS:
		v = nil
	case C.RuleActionTypeSniff:
		v = r.SniffOptions
	case C.RuleActionTypeResolve:
		v = r.ResolveOptions
	default:
		return nil, E.New("unknown rule action: " + r.Action)
	}
	if v == nil {
		return MarshallObjects((_RuleAction)(r))
	}
	return MarshallObjects((_RuleAction)(r), v)
}

func (r *RuleAction) UnmarshalJSON(data []byte) error {
	err := json.Unmarshal(data, (*_RuleAction)(r))
	if err != nil {
		return err
	}
	var v any
	switch r.Action {
	case "", C.RuleActionTypeRoute:
		r.Action = C.RuleActionTypeRoute
		v = &r.RouteOptions
	case C.RuleActionTypeReturn:
		v = nil
	case C.RuleActionTypeReject:
		v = &r.RejectOptions
	case C.RuleActionTypeHijackDNS:
		v = nil
	case C.RuleActionTypeSniff:
		v = &r.SniffOptions
	case C.RuleActionTypeResolve:
		v = &r.ResolveOptions
	default:
		return E.New("unknown rule action: " + r.Action)
	}
	if v == nil {
		// check unknown fields
		return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})
	}
	return UnmarshallExcluded(data, (*_RuleAction)(r), v)
}

type _DNSRuleAction struct {
	Action         string                `json:"action,omitempty"`
	RouteOptions   DNSRouteActionOptions `json:"-"`
	RejectOptions  RejectActionOptions   `json:"-"`
	SniffOptions   RouteActionSniff      `json:"-"`
	ResolveOptions RouteActionResolve    `json:"-"`
}

type DNSRuleAction _DNSRuleAction

func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
	var v any
	switch r.Action {
	case C.RuleActionTypeRoute:
		r.Action = ""
		v = r.RouteOptions
	case C.RuleActionTypeReturn:
		v = nil
	case C.RuleActionTypeReject:
		v = r.RejectOptions
	default:
		return nil, E.New("unknown DNS rule action: " + r.Action)
	}
	if v == nil {
		return MarshallObjects((_DNSRuleAction)(r))
	}
	return MarshallObjects((_DNSRuleAction)(r), v)
}

func (r *DNSRuleAction) UnmarshalJSON(data []byte) error {
	err := json.Unmarshal(data, (*_DNSRuleAction)(r))
	if err != nil {
		return err
	}
	var v any
	switch r.Action {
	case "", C.RuleActionTypeRoute:
		r.Action = C.RuleActionTypeRoute
		v = &r.RouteOptions
	case C.RuleActionTypeReturn:
		v = nil
	case C.RuleActionTypeReject:
		v = &r.RejectOptions
	default:
		return E.New("unknown DNS rule action: " + r.Action)
	}
	if v == nil {
		// check unknown fields
		return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{})
	}
	return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v)
}

type RouteActionOptions struct {
	Outbound                  string `json:"outbound"`
	UDPDisableDomainUnmapping bool   `json:"udp_disable_domain_unmapping,omitempty"`
}

type DNSRouteActionOptions struct {
	Server       string      `json:"server"`
	DisableCache bool        `json:"disable_cache,omitempty"`
	RewriteTTL   *uint32     `json:"rewrite_ttl,omitempty"`
	ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
}

type _RejectActionOptions struct {
	Method string `json:"method,omitempty"`
}

type RejectActionOptions _RejectActionOptions

func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
	err := json.Unmarshal(bytes, (*_RejectActionOptions)(r))
	if err != nil {
		return err
	}
	switch r.Method {
	case "", C.RuleActionRejectMethodDefault:
		r.Method = C.RuleActionRejectMethodDefault
	case C.RuleActionRejectMethodReset,
		C.RuleActionRejectMethodNetworkUnreachable,
		C.RuleActionRejectMethodHostUnreachable,
		C.RuleActionRejectMethodPortUnreachable,
		C.RuleActionRejectMethodDrop:
	default:
		return E.New("unknown reject method: " + r.Method)
	}
	return nil
}

type RouteActionSniff struct {
	Sniffer Listable[string] `json:"sniffer,omitempty"`
	Timeout Duration         `json:"timeout,omitempty"`
}

type RouteActionResolve struct {
	Strategy DomainStrategy `json:"strategy,omitempty"`
	Server   string         `json:"server,omitempty"`
}