package net_sniff

import (
	"os"
	"regexp"
	"time"

	"github.com/bettercap/bettercap/v2/log"
	"github.com/bettercap/bettercap/v2/network"
	"github.com/bettercap/bettercap/v2/session"

	"github.com/gopacket/gopacket/pcap"
	"github.com/gopacket/gopacket/pcapgo"

	"github.com/evilsocket/islazy/tui"
)

type SnifferContext struct {
	Handle       *pcap.Handle
	Interface    string
	Source       string
	DumpLocal    bool
	Verbose      bool
	Filter       string
	Expression   string
	Compiled     *regexp.Regexp
	Output       string
	OutputFile   *os.File
	OutputWriter *pcapgo.NgWriter
}

func (mod *Sniffer) GetContext() (error, *SnifferContext) {
	var err error

	ctx := NewSnifferContext()

	if err, ctx.Source = mod.StringParam("net.sniff.source"); err != nil {
		return err, ctx
	}

	if err, ctx.Interface = mod.StringParam("net.sniff.interface"); err != nil {
		return err, ctx
	}

	if ctx.Interface == "" {
		ctx.Interface = mod.Session.Interface.Name()
	}

	if ctx.Source == "" {
		/*
		 * We don't want to pcap.BlockForever otherwise pcap_close(handle)
		 * could hang waiting for a timeout to expire ...
		 */

		readTimeout := 500 * time.Millisecond
		if ctx.Handle, err = network.CaptureWithTimeout(ctx.Interface, readTimeout); err != nil {
			return err, ctx
		}
	} else {
		if ctx.Handle, err = pcap.OpenOffline(ctx.Source); err != nil {
			return err, ctx
		}
	}

	if err, ctx.Verbose = mod.BoolParam("net.sniff.verbose"); err != nil {
		return err, ctx
	}

	if err, ctx.DumpLocal = mod.BoolParam("net.sniff.local"); err != nil {
		return err, ctx
	}

	if err, ctx.Filter = mod.StringParam("net.sniff.filter"); err != nil {
		return err, ctx
	} else if ctx.Filter != "" {
		err = ctx.Handle.SetBPFFilter(ctx.Filter)
		if err != nil {
			return err, ctx
		}
	}

	if err, ctx.Expression = mod.StringParam("net.sniff.regexp"); err != nil {
		return err, ctx
	} else if ctx.Expression != "" {
		if ctx.Compiled, err = regexp.Compile(ctx.Expression); err != nil {
			return err, ctx
		}
	}

	if err, ctx.Output = mod.StringParam("net.sniff.output"); err != nil {
		return err, ctx
	} else if ctx.Output != "" {
		if ctx.OutputFile, err = os.Create(ctx.Output); err != nil {
			return err, ctx
		}

		ctx.OutputWriter, err = pcapgo.NewNgWriter(ctx.OutputFile, ctx.Handle.LinkType())
		if err != nil {
			return err, ctx
		}
	}

	return nil, ctx
}

func NewSnifferContext() *SnifferContext {
	return &SnifferContext{
		Handle:       nil,
		Interface:    "",
		Source:       "",
		DumpLocal:    false,
		Verbose:      false,
		Filter:       "",
		Expression:   "",
		Compiled:     nil,
		Output:       "",
		OutputFile:   nil,
		OutputWriter: nil,
	}
}

var (
	no  = tui.Red("no")
	yes = tui.Green("yes")
	yn  = map[bool]string{
		true:  yes,
		false: no,
	}
)

func (c *SnifferContext) Log(sess *session.Session) {
	log.Info("Interface          : %s", tui.Bold(c.Interface))
	log.Info("Skip local packets : %s", yn[!c.DumpLocal])
	log.Info("Verbose            : %s", yn[c.Verbose])
	log.Info("BPF Filter         : '%s'", tui.Yellow(c.Filter))
	log.Info("Regular expression : '%s'", tui.Yellow(c.Expression))
	log.Info("File output        : '%s'", tui.Yellow(c.Output))
}

func (c *SnifferContext) Close() {
	if c.Handle != nil {
		log.Debug("closing handle")
		c.Handle.Close()
		log.Debug("handle closed")
		c.Handle = nil
	}

	if c.OutputFile != nil {
		log.Debug("closing output")
		c.OutputFile.Close()
		log.Debug("output closed")
		c.OutputFile = nil
	}
}
