package ports

import (
	"agent/client"
	"agent/commons/debug"
	"agent/commons/utime"
	"agent/modules/ports/api"
	"agent/modules/ports/cmds"
	"agent/modules/ports/internal"
	"context"
	"sync"

	"github.com/google/uuid"
)

type portsModule struct {
	mu    *sync.Mutex
	ports []uuid.UUID
}

func (m *portsModule) Init(ctx context.Context) (err error) {
	m.mu = &sync.Mutex{}
	m.ports = nil
	return nil
}

func (m *portsModule) Name() string {
	return "Ports"
}

func (m *portsModule) Commands() []client.ICommand {
	return cmds.InitItems
}

func (m *portsModule) Run(ctx context.Context, wg *sync.WaitGroup) {
	defer func() {
		wg.Done()
	}()
	m.run(ctx)
}

func (m *portsModule) OnLogin(ctx context.Context, config map[string]any) (err error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	plugins, ok := config["plugins"].(map[string]any)
	if !ok {
		return nil
	}

	plugin, ok := plugins["tunnel_ports"].(map[string]any)
	if !ok {
		return nil
	}

	if portsData, ok := plugin["uuids"].([]any); ok {
		for _, v := range portsData {
			if portStr, ok := v.(string); ok {
				portUuid, err := uuid.Parse(portStr)
				if err != nil {
					return err
				}
				m.ports = append(m.ports, portUuid)
			}
		}
	}

	return nil
}

func (m *portsModule) popPorts() (ports []uuid.UUID) {
	m.mu.Lock()
	defer m.mu.Unlock()
	ports = m.ports[:]
	m.ports = nil
	return ports
}

func (m *portsModule) isPorts() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.ports) > 0
}

func (m *portsModule) run(ctx context.Context) {
	cl := client.ExtractClient(ctx)
	for {
		select {
		case <-ctx.Done():
			return
		default:
		}

		utime.Sleep(utime.Second * 1)

		if cl.IsAuthorized() && m.isPorts() {
			// Извлекаем список портов, которые агенту нужно открыть. Операция так же обнуляет список, чтобы не было
			// повторного открытия.
			ports := m.popPorts()

			for _, portUuid := range ports {
				// Запрашиваем информацию о порте
				port, err := api.AgentTunnelPortGet(ctx, cl, portUuid)
				if err != nil {
					// Логируем ошибку и переходим к следующему порту
					debug.Logf("Error, AgentTunnelPortGet(%s): %s", portUuid, err)
					continue
				}

				if port.Uuid == uuid.Nil {
					// Пропускаем порт, он уже закрыт
					break
				}

				_ = m.newPort(ctx, cl, port)
			}
		}
	}
}

func (m *portsModule) newPort(ctx context.Context, cl client.IClient, port api.PortParams) (err error) {
	defer func() {
		status := api.PortStatusOk
		if err != nil {
			status = api.PortStatusError
		}
		if err2 := api.AgentTunnelPortUpdate(ctx, cl, port.Uuid, status, err); err2 != nil {
			debug.Logf("Error, NewTunnelPortUpdate(%s): %s", port.Uuid, err)
		}
	}()
	// Запускаем порт
	switch port.Mode {
	// Open
	case 1:
		tunPort, err := internal.NewTunnelPortOpen(cl, port)
		if err != nil {
			// Если произошла ошибка, порт будет исключен из списка открытых портов
			debug.Logf("Error, NewTunnelPortOpen(%s): %s", port.Uuid, err)
			return err
		}
		go tunPort.Run(ctx)
	// Listen
	case 2:
		tunPort, err := internal.NewTunnelPortListen(cl, port)
		if err != nil {
			// Если произошла ошибка, порт будет исключен из списка открытых портов
			debug.Logf("Error, NewTunnelPortRedirect(%s): %s", port.Uuid, err)
			return err
		}
		go tunPort.Run(ctx)
	}
	return nil
}
