package action

import (
	"kf2-antiddos/internal/output"

	"os"
	"os/exec"
	"strings"
	"time"
)

type Action struct {
	ticker      *time.Ticker
	ips         map[string]bool // map[IP]readyToUnban
	allowAction string
	denyAction  string
	shell       string
	quit        chan struct{}
	banChan     *chan string
	resetChan   *chan string
	workerID    uint
}

func New(workerID uint, denyTime uint, shell, allowAction, denyAction string, banChan, resetChan *chan string) *Action {
	return &Action{
		ticker:      time.NewTicker(time.Duration(denyTime) * time.Second),
		ips:         make(map[string]bool),
		allowAction: allowAction,
		denyAction:  denyAction,
		shell:       shell,
		quit:        make(chan struct{}),
		banChan:     banChan,
		resetChan:   resetChan,
		workerID:    workerID,
	}
}

func (a *Action) Do() {
	go func() {
		for {
			select {
			case ip := <-*a.banChan:
				a.deny(ip)
			case <-a.ticker.C:
				a.allow(false)
			case <-a.quit:
				a.ticker.Stop()
				a.allow(true)
				return
			}
		}
	}()
}

func (a *Action) Stop() {
	close(a.quit)
}

func (a *Action) allow(unbanAll bool) {
	unban := make([]string, 0)

	for ip := range a.ips {
		if unbanAll || a.ips[ip] { // aka if readyToUnban
			unban = append(unban, ip)
		} else {
			a.ips[ip] = true // mark readyToUnban next time
		}
	}

	for _, ip := range unban {
		delete(a.ips, ip)
	}

	if len(unban) != 0 {
		for _, ip := range unban {
			*a.resetChan <- ip
		}
		output.Printf("Allow: %s", strings.Join(unban, ", "))

		if err := a.execCmd(a.allowAction, unban); err != nil {
			output.Error(err.Error())
			return
		}
	}
}

func (a *Action) deny(ip string) {
	a.ips[ip] = false

	output.Printf("Ban: %s", ip)

	if err := a.execCmd(a.denyAction, []string{ip}); err != nil {
		output.Error(err.Error())
		return
	}
}

func (a *Action) execCmd(command string, args []string) error {
	WorkingDir, err := os.Getwd()
	if err != nil {
		WorkingDir = ""
	}
	cmd := &exec.Cmd{
		Path:   a.shell,
		Args:   append([]string{a.shell, command}, args...),
		Stdout: output.StdoutWriter(),
		Stderr: output.StderrWriter(),
		Dir:    WorkingDir,
	}

	if err := cmd.Start(); err != nil {
		return err
	}
	if err := cmd.Wait(); err != nil {
		return err
	}
	return nil
}