// Package iptables contains code for managing firewall rules. This package
// really only works reliably on Linux. In all other systems the functionality
// in here is just a set of stubs returning errors.
package iptables

import (
	"github.com/ooni/probe-cli/v3/internal/runtimex"
)

type shell interface {
	createChains() error
	dropIfDestinationEquals(ip string) error
	rstIfDestinationEqualsAndIsTCP(ip string) error
	dropIfContainsKeywordHex(keyword string) error
	dropIfContainsKeyword(keyword string) error
	rstIfContainsKeywordHexAndIsTCP(keyword string) error
	rstIfContainsKeywordAndIsTCP(keyword string) error
	hijackDNS(address string) error
	hijackHTTPS(address string) error
	hijackHTTP(address string) error
	waive() error
}

// CensoringPolicy implements a censoring policy.
type CensoringPolicy struct {
	DropIPs            []string // drop IP traffic to these IPs
	DropKeywordsHex    []string // drop IP packets with these hex keywords
	DropKeywords       []string // drop IP packets with these keywords
	HijackDNSAddress   string   // where to hijack DNS to
	HijackHTTPSAddress string   // where to hijack HTTPS to
	HijackHTTPAddress  string   // where to hijack HTTP to
	ResetIPs           []string // RST TCP/IP traffic to these IPs
	ResetKeywordsHex   []string // RST TCP/IP flows with these hex keywords
	ResetKeywords      []string // RST TCP/IP flows with these keywords
	sh                 shell
}

// NewCensoringPolicy returns a new censoring policy.
func NewCensoringPolicy() *CensoringPolicy {
	return &CensoringPolicy{
		sh: newShell(),
	}
}

// Apply applies the censorship policy
func (c *CensoringPolicy) Apply() (err error) {
	defer func() {
		if recover() != nil {
			// JUST KNOW WE'VE BEEN HERE
		}
	}()
	err = c.sh.createChains()
	runtimex.PanicOnError(err, "c.sh.createChains failed")
	// Implementation note: we want the RST rules to be first such
	// that we end up enforcing them before the drop rules.
	for _, keyword := range c.ResetKeywordsHex {
		err = c.sh.rstIfContainsKeywordHexAndIsTCP(keyword)
		runtimex.PanicOnError(err, "c.sh.rstIfContainsKeywordHexAndIsTCP failed")
	}
	for _, keyword := range c.ResetKeywords {
		err = c.sh.rstIfContainsKeywordAndIsTCP(keyword)
		runtimex.PanicOnError(err, "c.sh.rstIfContainsKeywordAndIsTCP failed")
	}
	for _, ip := range c.ResetIPs {
		err = c.sh.rstIfDestinationEqualsAndIsTCP(ip)
		runtimex.PanicOnError(err, "c.sh.rstIfDestinationEqualsAndIsTCP failed")
	}
	for _, keyword := range c.DropKeywordsHex {
		err = c.sh.dropIfContainsKeywordHex(keyword)
		runtimex.PanicOnError(err, "c.sh.dropIfContainsKeywordHex failed")
	}
	for _, keyword := range c.DropKeywords {
		err = c.sh.dropIfContainsKeyword(keyword)
		runtimex.PanicOnError(err, "c.sh.dropIfContainsKeyword failed")
	}
	for _, ip := range c.DropIPs {
		err = c.sh.dropIfDestinationEquals(ip)
		runtimex.PanicOnError(err, "c.sh.dropIfDestinationEquals failed")
	}
	if c.HijackDNSAddress != "" {
		err = c.sh.hijackDNS(c.HijackDNSAddress)
		runtimex.PanicOnError(err, "c.sh.hijackDNS failed")
	}
	if c.HijackHTTPSAddress != "" {
		err = c.sh.hijackHTTPS(c.HijackHTTPSAddress)
		runtimex.PanicOnError(err, "c.sh.hijackHTTPS failed")
	}
	if c.HijackHTTPAddress != "" {
		err = c.sh.hijackHTTP(c.HijackHTTPAddress)
		runtimex.PanicOnError(err, "c.sh.hijackHTTP failed")
	}
	return
}

// Waive removes any censorship policy
func (c *CensoringPolicy) Waive() error {
	return c.sh.waive()
}
