package chips

import (
	"context"
	"log/slog"
	"net/http"
	"sync"
	"time"
)

type SettingsFunc func(*TokenObject) error

type ChipBag struct {
	sync.Mutex
	logger       *slog.Logger
	chips        map[string]*GuacTunnel
	total_conns  int
	settingsFunc SettingsFunc
}

type ChipBagOpt func(*ChipBag) error

func New(opts ...ChipBagOpt) *ChipBag {
	chips := &ChipBag{
		logger:       slog.Default(),
		chips:        make(map[string]*GuacTunnel),
		total_conns:  0,
		settingsFunc: NoopSettingsFunc,
	}

	for _, opt := range opts {
		if err := opt(chips); err != nil {
			slog.Default().Error("failed to apply ChipBag option", "err", err)
			return nil
		}
	}
	go chips.processClosedTunnels()
	return chips
}

func (cb *ChipBag) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
	cb.logger.Info("handling WebSocket connection", "remote_addr", r.RemoteAddr)
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		cb.logger.Error("failed to upgrade connection to websocket", "err", err)
		return
	}

	cb.logger.Info("websocket connection established", "remote_addr", r.RemoteAddr)

	query := r.URL.RawQuery

	guacd, err := NewGuacdClient(conn, query, cb.logger, cb.settingsFunc)
	if err != nil {
		cb.logger.Error("failed to create GuacdClient", "err", err)
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Internal Server Error"))
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	id := guacd.GetConnectionId()
	for id == "" {
		select {
		case <-ctx.Done():
			cb.logger.Error("timeout waiting for GuacdClient connection ID", "err", "request timed out")
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("Internal Server Error"))
			return
		default:
			cb.logger.Debug("waiting for GuacdClient connection ID")
			id = guacd.GetConnectionId()
		}
	}

	cb.Lock()
	cb.total_conns++

	tunnel := &GuacTunnel{
		id:         id,
		GuacTunnel: guacd,
	}

	cb.chips[tunnel.id] = tunnel
	cb.Unlock()

}

func (cb *ChipBag) processClosedTunnels() {
	for {
		cb.logger.Debug("checking for closed tunnels", "total_chips", cb.total_conns)
		for _, tunnel := range cb.chips {
			if tunnel.GuacTunnel.State() == StateClosed {
				cb.Lock()
				delete(cb.chips, tunnel.id)
				cb.total_conns--
				cb.Unlock()
			}
		}
		time.Sleep(5 * time.Second) // Adjust the interval as needed
	}
}

func (cb *ChipBag) GetChip(id string) (*GuacTunnel, error) {
	cb.Lock()
	defer cb.Unlock()

	tunnel, exists := cb.chips[id]
	if !exists {
		return nil, ErrChipNotFound
	}

	return tunnel, nil
}
