package proc

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"

	"github.com/pranshuparmar/witr/pkg/model"
)

func ListOpenPorts() ([]model.OpenPort, error) {
	out, err := exec.Command("netstat", "-ano").Output()
	if err != nil {
		return nil, err
	}

	lines := strings.Split(string(out), "\n")
	var ports []model.OpenPort
	seen := make(map[string]bool)

	for _, line := range lines {
		fields := strings.Fields(line)
		// TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 888  (len 5)
		// UDP 0.0.0.0:123 *:*       999            (len 4)

		if len(fields) < 4 {
			continue
		}

		proto := fields[0]
		if proto != "TCP" && proto != "UDP" && proto != "TCPv6" && proto != "UDPv6" {
			continue
		}

		var pidStr, state string
		if len(fields) == 4 {
			pidStr = fields[3]
			state = "LISTEN"
		} else if len(fields) >= 5 {
			pidStr = fields[4]
			state = fields[3]
			if state == "LISTENING" {
				state = "LISTEN"
			}
		}

		pid, err := strconv.Atoi(pidStr)
		if err != nil {
			continue
		}

		localAddr := fields[1]
		lastColon := strings.LastIndex(localAddr, ":")
		if lastColon == -1 {
			continue
		}
		portStr := localAddr[lastColon+1:]
		ip := localAddr[:lastColon]
		if len(ip) > 2 && strings.HasPrefix(ip, "[") && strings.HasSuffix(ip, "]") {
			ip = ip[1 : len(ip)-1]
		}

		port, err := strconv.Atoi(portStr)
		if err == nil {
			key := fmt.Sprintf("%d|%d|%s", pid, port, ip)
			if !seen[key] {
				ports = append(ports, model.OpenPort{
					PID:      pid,
					Port:     port,
					Address:  ip,
					Protocol: proto,
					State:    state,
				})
				seen[key] = true
			}
		}
	}
	return ports, nil
}

// GetSocketsForPID returns every IP socket owned by a PID, including
// non-listening sockets, by parsing `netstat -ano`.
func GetSocketsForPID(pid int) []model.Socket {
	out, err := exec.Command("netstat", "-ano").Output()
	if err != nil {
		return nil
	}

	lines := strings.Split(string(out), "\n")
	var sockets []model.Socket
	seen := make(map[string]bool)

	pidStr := strconv.Itoa(pid)

	for _, line := range lines {
		fields := strings.Fields(line)
		// TCP:  Proto LocalAddr ForeignAddr State PID  (5 fields)
		// UDP:  Proto LocalAddr *:*         PID        (4 fields)
		if len(fields) < 4 {
			continue
		}

		proto := strings.ToUpper(fields[0])
		var matchPID, state string
		if strings.HasPrefix(proto, "TCP") {
			if len(fields) < 5 {
				continue
			}
			state = fields[3]
			if state == "LISTENING" {
				state = "LISTEN"
			}
			matchPID = fields[4]
		} else if strings.HasPrefix(proto, "UDP") {
			state = "OPEN"
			matchPID = fields[3]
		} else {
			continue
		}

		if matchPID != pidStr {
			continue
		}

		localAddr := fields[1]
		lastColon := strings.LastIndex(localAddr, ":")
		if lastColon == -1 {
			continue
		}
		portStr := localAddr[lastColon+1:]
		ip := localAddr[:lastColon]
		if len(ip) > 2 && strings.HasPrefix(ip, "[") && strings.HasSuffix(ip, "]") {
			ip = ip[1 : len(ip)-1]
		}

		port, err := strconv.Atoi(portStr)
		if err != nil {
			continue
		}
		key := proto + "|" + ip + "|" + portStr + "|" + state
		if seen[key] {
			continue
		}
		seen[key] = true
		sockets = append(sockets, model.Socket{
			Port:     port,
			Address:  ip,
			Protocol: proto,
			State:    state,
		})
	}
	return sockets
}
