package system

import (
	"context"
	"strconv"
	"strings"

	"github.com/goss-org/GOnetstat"
	"github.com/goss-org/goss/util"
)

type Port interface {
	Port() string
	Exists() (bool, error)
	Listening() (bool, error)
	IP() ([]string, error)
}

type DefPort struct {
	port     string
	sysPorts map[string][]GOnetstat.Process
}

func NewDefPort(_ context.Context, port string, system *System, config util.Config) Port {
	p := normalizePort(port)
	return &DefPort{
		port:     p,
		sysPorts: system.Ports(),
	}
}

func splitPort(fullport string) (network, port string) {
	split := strings.SplitN(fullport, ":", 2)
	if len(split) == 2 {
		return split[0], split[1]
	}
	return "tcp", fullport

}

func normalizePort(fullport string) string {
	net, addr := splitPort(fullport)
	return net + ":" + addr
}

func (p *DefPort) Port() string {
	return p.port
}

func (p *DefPort) Exists() (bool, error) { return p.Listening() }

func (p *DefPort) Listening() (bool, error) {
	if _, ok := p.sysPorts[p.port]; ok {
		return true, nil
	}
	return false, nil
}

func (p *DefPort) IP() ([]string, error) {
	var ips []string
	for _, entry := range p.sysPorts[p.port] {
		ips = append(ips, entry.Ip)
	}
	return ips, nil
}

// FIXME: Is there a better way to do this rather than ignoring errors?
func GetPorts(lookupPids bool) map[string][]GOnetstat.Process {
	ports := make(map[string][]GOnetstat.Process)
	netstat, _ := GOnetstat.Tcp(lookupPids)
	var net string
	// netPorts := make(map[string]GOnetstat.Process)
	// ports["tcp"] = netPorts
	net = "tcp"
	for _, entry := range netstat {
		if entry.State == "LISTEN" {
			port := strconv.FormatInt(entry.Port, 10)
			ports[net+":"+port] = append(ports[net+":"+port], entry)
		}
	}
	netstat, _ = GOnetstat.Tcp6(lookupPids)
	// netPorts = make(map[string]GOnetstat.Process)
	// ports["tcp6"] = netPorts
	net = "tcp6"
	for _, entry := range netstat {
		if entry.State == "LISTEN" {
			port := strconv.FormatInt(entry.Port, 10)
			ports[net+":"+port] = append(ports[net+":"+port], entry)
		}
	}
	netstat, _ = GOnetstat.Udp(lookupPids)
	// netPorts = make(map[string]GOnetstat.Process)
	// ports["udp"] = netPorts
	net = "udp"
	for _, entry := range netstat {
		port := strconv.FormatInt(entry.Port, 10)
		ports[net+":"+port] = append(ports[net+":"+port], entry)
	}
	netstat, _ = GOnetstat.Udp6(lookupPids)
	// netPorts = make(map[string]GOnetstat.Process)
	// ports["udp6"] = netPorts
	net = "udp6"
	for _, entry := range netstat {
		port := strconv.FormatInt(entry.Port, 10)
		ports[net+":"+port] = append(ports[net+":"+port], entry)
	}
	return ports
}
