// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package port

import (
	"context"
	"fmt"
	"log/slog"
	"net"

	"github.com/derailed/k9s/internal/slogs"
)

// PortTunnels represents a collection of tunnels.
type PortTunnels []PortTunnel

// CheckAvailable checks if all port tunnels are available.
func (t PortTunnels) CheckAvailable(ctx context.Context) error {
	for _, pt := range t {
		if !IsPortFree(ctx, pt) {
			return fmt.Errorf("port %s is not available on host", pt.LocalPort)
		}
	}

	return nil
}

// PortTunnel represents a host tunnel port mapper.
type PortTunnel struct {
	Address, Container, LocalPort, ContainerPort string
}

// NewPortTunnel returns a new instance.
func NewPortTunnel(a, co, lp, cp string) PortTunnel {
	return PortTunnel{
		Address:       a,
		Container:     co,
		LocalPort:     lp,
		ContainerPort: cp,
	}
}

// String dumps as string.
func (t PortTunnel) String() string {
	return fmt.Sprintf("%s|%s|%s:%s", t.Address, t.Container, t.LocalPort, t.ContainerPort)
}

// PortMap returns a port mapping.
func (t PortTunnel) PortMap() string {
	if t.LocalPort == "" {
		t.LocalPort = t.ContainerPort
	}

	return t.LocalPort + ":" + t.ContainerPort
}

// IsPortFree checks if a address/port pair is available on host.
func IsPortFree(ctx context.Context, t PortTunnel) bool {
	var ncfg net.ListenConfig
	s, err := ncfg.Listen(ctx, "tcp", fmt.Sprintf("%s:%s", t.Address, t.LocalPort))
	if err != nil {
		slog.Warn("Port is not available", slogs.Port, t.LocalPort, slogs.Address, t.Address)
		return false
	}

	return s.Close() == nil
}
