package ports

import (
	"context"
	"fmt"
	"net"
	"time"

	"go.uber.org/zap"
)

type Server struct {
	EnabledPortsTCP []string
	EnabledPortsUDP []string
	IP              string
	Logger          *zap.SugaredLogger

	store Store
}

func New(store Store) *Server {
	return &Server{store: store, Logger: zap.NewNop().Sugar()}
}

func (s *Server) Start(ctx context.Context) error {
	for _, port := range s.EnabledPortsTCP {
		portCtx, cancel := context.WithCancel(ctx)
		go s.doListenTCP(portCtx, port)
		defer cancel()
	}

	<-ctx.Done()
	return nil
}

func (s *Server) AddTCPPort(port string) {
	s.EnabledPortsTCP = append(s.EnabledPortsTCP, port)
}

func (s *Server) doListenTCP(ctx context.Context, port string) error {
	lAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(s.IP, port))
	if err != nil {
		s.Logger.Warnw("Error resoling listening addr.", "network", "tcp", "port", port)
		return fmt.Errorf("error resolving listening address: %w", err)
	}

	listener, err := net.ListenTCP("tcp", lAddr)
	if err != nil {
		return fmt.Errorf("error starting listener: %w", err)
	}
	defer listener.Close()

	go func() {
		<-ctx.Done()
		s.Logger.Debug("Listener context cancelled.")
		listener.Close()
	}()

	s.Logger.Infow("Listening for connections to TCP port.", "port", port)
	for {
		conn, err := listener.Accept()
		if err != nil {
			select {
			case <-ctx.Done():
				s.Logger.Infow("Listener shutting down.", "port", port, "network", "tcp")
				return nil
			default:
				s.Logger.Infow("Error accepting connection.", "error", err)
				continue
			}
		}

		s.Logger.Infow("Got connection on port.", "port", port, "network", "tcp", "remote_addr", conn.RemoteAddr().String())

		conn.SetReadDeadline(time.Now().Add(time.Second * 15))

		buf := make([]byte, 256)

		_, err = conn.Read(buf)
		if err != nil {
			buf = []byte{}
		}

		attempt := &ConnectionAttempt{
			Port:    port,
			Network: "tcp",
			From:    conn.RemoteAddr().String(),
			Data:    buf,
		}
		conn.Close()

		if err := s.store.Add(attempt); err != nil {
			s.Logger.Warnw("Error storing attempt in store.", "error", err)
		}
	}
}
