package web

import (
	"net/http"

	"github.com/amir20/dozzle/internal/auth"
	"github.com/go-chi/chi/v5"
	"github.com/gorilla/websocket"
	"github.com/rs/zerolog/log"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func (h *handler) attach(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error().Err(err).Msg("error while trying to upgrade connection")
		return
	}
	defer conn.Close()

	id := chi.URLParam(r, "id")
	userLabels := h.config.Labels
	permit := true
	if h.config.Authorization.Provider != NONE {
		user := auth.UserFromContext(r.Context())
		if user.ContainerLabels.Exists() {
			userLabels = user.ContainerLabels
		}
		permit = user.Roles.Has(auth.Shell)
	}

	if !permit {
		log.Warn().Msg("user is not permitted to attach to container")
		conn.WriteMessage(websocket.TextMessage, []byte("⛔ Access denied: attaching to this container is forbidden\r\n"))
		return
	}

	containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
	if err != nil {
		log.Error().Err(err).Msg("error while trying to find container")
		return
	}

	wsReader := &webSocketReader{conn: conn}
	wsWriter := &webSocketWriter{conn: conn}
	if err = containerService.Attach(r.Context(), wsReader, wsWriter); err != nil {
		log.Error().Err(err).Msg("error while trying to attach to container")
		conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
		return
	}
}

func (h *handler) exec(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error().Err(err).Msg("error while trying to upgrade connection")
		return
	}
	defer conn.Close()

	id := chi.URLParam(r, "id")
	userLabels := h.config.Labels
	permit := true
	if h.config.Authorization.Provider != NONE {
		user := auth.UserFromContext(r.Context())
		if user.ContainerLabels.Exists() {
			userLabels = user.ContainerLabels
		}
		permit = user.Roles.Has(auth.Shell)
	}

	if !permit {
		log.Warn().Msg("user is not permitted to exec into container")
		conn.WriteMessage(websocket.TextMessage, []byte("⛔ Access denied: attaching to this container is forbidden\r\n"))
		return
	}

	containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
	if err != nil {
		log.Error().Err(err).Msg("error while trying to find container")
		return
	}

	wsReader := &webSocketReader{conn: conn}
	wsWriter := &webSocketWriter{conn: conn}
	if err = containerService.Exec(r.Context(), []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"}, wsReader, wsWriter); err != nil {
		log.Error().Err(err).Msg("error while trying to attach to container")
		conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
		return
	}
}

type webSocketWriter struct {
	conn *websocket.Conn
}

func (w *webSocketWriter) Write(p []byte) (int, error) {
	err := w.conn.WriteMessage(websocket.TextMessage, p)
	return len(p), err
}

type webSocketReader struct {
	conn   *websocket.Conn
	buffer []byte
}

func (r *webSocketReader) Read(p []byte) (n int, err error) {
	if len(r.buffer) > 0 {
		n = copy(p, r.buffer)
		r.buffer = r.buffer[n:]
		return n, nil
	}

	// Otherwise, read a new message
	_, message, err := r.conn.ReadMessage()
	if err != nil {
		return 0, err
	}

	n = copy(p, message)

	// If we couldn't copy the entire message, store the rest in our buffer
	if n < len(message) {
		r.buffer = message[n:]
	}

	return n, nil
}
