package inbound

import (
	"encoding/json"
	"io"
	"net/http"

	C "github.com/sagernet/sing-box/constant"
	"github.com/sagernet/sing-box/option"
	"github.com/sagernet/sing/common"
	E "github.com/sagernet/sing/common/exceptions"
	F "github.com/sagernet/sing/common/format"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/render"
)

func (h *ShadowsocksMulti) createHandler() http.Handler {
	router := chi.NewRouter()
	router.Get("/", h.handleHello)
	router.Put("/users", h.handleUpdateUsers)
	router.Get("/traffics", h.handleReadTraffics)
	return router
}

func (h *ShadowsocksMulti) handleHello(writer http.ResponseWriter, request *http.Request) {
	render.JSON(writer, request, render.M{
		"server":  "sing-box",
		"version": C.Version,
	})
}

func (h *ShadowsocksMulti) handleUpdateUsers(writer http.ResponseWriter, request *http.Request) {
	var users []option.ShadowsocksUser
	err := readRequest(request, &users)
	if err != nil {
		h.newError(E.Cause(err, "controller: update users: parse request"))
		writer.WriteHeader(http.StatusBadRequest)
		writer.Write([]byte(F.ToString(err)))
		return
	}
	users = append([]option.ShadowsocksUser{{
		Name:     "control",
		Password: h.users[0].Password,
	}}, users...)
	err = h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user option.ShadowsocksUser) int {
		return index
	}), common.Map(users, func(user option.ShadowsocksUser) string {
		return user.Password
	}))
	if err != nil {
		h.newError(E.Cause(err, "controller: update users"))
		writer.WriteHeader(http.StatusBadRequest)
		writer.Write([]byte(F.ToString(err)))
		return
	}
	h.users = users
	h.trafficManager.Reset()
	writer.WriteHeader(http.StatusNoContent)
	h.logger.Info("controller: updated ", len(users)-1, " users")
}

type ShadowsocksUserTraffic struct {
	Name     string `json:"name,omitempty"`
	Upload   uint64 `json:"upload,omitempty"`
	Download uint64 `json:"download,omitempty"`
}

func (h *ShadowsocksMulti) handleReadTraffics(writer http.ResponseWriter, request *http.Request) {
	h.logger.Debug("controller: traffics sent")
	trafficMap := h.trafficManager.ReadTraffics()
	if len(trafficMap) == 0 {
		writer.WriteHeader(http.StatusNoContent)
		return
	}
	traffics := make([]ShadowsocksUserTraffic, 0, len(trafficMap))
	for user, traffic := range trafficMap {
		traffics = append(traffics, ShadowsocksUserTraffic{
			Name:     h.users[user].Name,
			Upload:   traffic.Upload,
			Download: traffic.Download,
		})
	}
	render.JSON(writer, request, traffics)
}

func readRequest(request *http.Request, v any) error {
	defer request.Body.Close()
	content, err := io.ReadAll(request.Body)
	if err != nil {
		return err
	}
	return json.Unmarshal(content, v)
}