package internal

import (
	"agent/commons/debug"
	"agent/defines/derrs"
	"agent/internal/include"
	"agent/modules/ports/api"
	"agent/modules/ports/defines/merrs"
	"context"
	"fmt"
	"net"
	"strconv"
	"sync"
	"sync/atomic"

	"github.com/google/uuid"
)

func NewTunnelPortListen(cl include.IClient, params api.PortParams) (*tunPortListen, error) {
	cfg := cl.Config()
	if params.Host == "" {
		params.Host = "127.0.0.1"
	}
	if params.Port <= 0 {
		return nil, derrs.NewIncorrectParamsError("port <= 0")
	}
	if params.LinkTo.Host == "" {
		// Если хост не указан используем IP-адрес сервера
		params.LinkTo.Host = cfg.Server.Host
	}
	if params.LinkTo.Port == 0 {
		// Если порт не указан, используем порт сервера
		params.LinkTo.Port = cfg.Server.Port
	}
	return &tunPortListen{
		params: params,
	}, nil
}

type tunPortListen struct {
	params     api.PortParams
	runOnce    atomic.Bool
	lock       sync.Mutex
	listener   net.Listener
	agentConn  net.Conn
	remoteConn net.Conn
}

func (p *tunPortListen) Params() api.PortParams {
	return p.params
}

func (p *tunPortListen) Close() error {
	go func() {
		p.lock.Lock()
		defer p.lock.Unlock()
		if p.params.Uuid != uuid.Nil {
			Ports.Delete(p.params.Uuid)
		}
		if p.listener != nil {
			p.listener.Close()
		}
		if p.agentConn != nil {
			p.agentConn.Close()
		}
		if p.remoteConn != nil {
			p.remoteConn.Close()
		}
	}()
	return nil
}

func (p *tunPortListen) Run(ctx context.Context) (err error) {
	if p.runOnce.CompareAndSwap(false, true) {
		if _, ok := Ports.Load(p.params.Uuid); ok {
			// Порт уже запущен
			return nil
		}

		Ports.Store(p.params.Uuid, p)
		defer func() {
			if p.params.Uuid != uuid.Nil {
				Ports.Delete(p.params.Uuid)
			}
		}()

		addr := net.JoinHostPort(p.params.Host, strconv.Itoa(p.params.Port))
		// Открываем порт на прослушивание
		if p.listener, err = p.listen(addr); err != nil {
			return err
		}
		defer p.listener.Close()

		for {
			_, ok := Ports.Load(p.params.Uuid)
			if !ok {
				// Порт был закрыт
				return
			}
			if p.agentConn, err = p.accept(); err != nil {
				continue
			}
			go p.handleConn(ctx, p.agentConn)
		}
	}
	return nil
}

func (p *tunPortListen) handleConn(ctx context.Context, conn net.Conn) {
	defer conn.Close()

	// Подключаемся к удаленному серверу
	remoteConn, err := p.connectToRemote(p.params.LinkTo.Host, p.params.LinkTo.Port)
	if err != nil {
		debug.Log("Error connecting to remote server:", err)
		return
	}
	defer remoteConn.Close()

	debug.Logf(
		"Redirecting traffic from %s:%d to %s:%d\n",
		p.params.Host, p.params.Port, p.params.LinkTo.Host, p.params.LinkTo.Port,
	)

	// Переадресация данных между клиентом и удаленным сервером
	go proxy(remoteConn, conn)
	proxy(conn, remoteConn)
}

func (p *tunPortListen) listen(addr string) (listener net.Listener, err error) {
	p.lock.Lock()
	defer p.lock.Unlock()
	if p.listener, err = net.Listen("tcp", addr); err != nil {
		debug.Logf(`Error listening on "%s": %v`, addr, err)
		return nil, err
	}
	debug.Logf(`Listening on "%s"`, addr)
	return p.listener, nil
}

func (p *tunPortListen) accept() (conn net.Conn, err error) {
	if conn, err = p.listener.Accept(); err != nil {
		debug.Logf(`Error accepting connection: %v`, err)
		return nil, err
	}

	p.lock.Lock()
	defer p.lock.Unlock()
	p.agentConn = conn

	debug.Logf(`Accepted connection from "%s"`, conn.RemoteAddr())

	return conn, nil
}

func (p *tunPortListen) connectToRemote(host string, port int) (srvConn net.Conn, err error) {
	p.lock.Lock()
	defer p.lock.Unlock()
	p.remoteConn, err = connectTo(host, port)
	if err != nil {
		return nil, merrs.Join(err, merrs.NewFailedConnectToError(fmt.Sprintf("%s:%d", host, port)))
	}
	return p.remoteConn, nil
}
