package api

import (
	"encoding/json"
	"net/http"
	"sort"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/alertstore/pkg/subscription"
)

// @Summary	List subscriptions
// @Id			listSubscriptions
// @Tags		Subscriptions
// @Produce	json
// @Success	200	{array}		subscription.Subscription
// @Failure	500	{object}	ErrorResponse
// @Router		/subscriptions [get]
func (s *Server) listSubscriptions(c *gin.Context) {
	subs, err := s.subStore.List(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, errorBody(err))
		return
	}
	c.JSON(http.StatusOK, subs)
}

type channelResponse struct {
	ID   string `json:"id" validate:"required"`
	Name string `json:"name" validate:"required"`
}

// listChannels backs the subscription channel picker: the UI offers the groups
// the bot already belongs to instead of asking the operator for a chat ID.
//
//	@Summary	List the IM channels the bot belongs to
//	@Id			listChannels
//	@Tags		Subscriptions
//	@Produce	json
//	@Success	200	{array}		channelResponse
//	@Failure	502	{object}	ErrorResponse	"IM provider unreachable"
//	@Router		/channels [get]
func (s *Server) listChannels(c *gin.Context) {
	channels, err := s.im.ListChannels(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusBadGateway, errorMessage("cannot list channels: "+err.Error()))
		return
	}
	out := make([]channelResponse, 0, len(channels))
	for _, channel := range channels {
		out = append(out, channelResponse{ID: channel.ID, Name: channel.Name})
	}
	// Unnamed groups sort by ID so the picker order stays stable across reloads.
	sort.Slice(out, func(i, j int) bool {
		li, lj := strings.ToLower(out[i].Name), strings.ToLower(out[j].Name)
		if li != lj {
			return li < lj
		}
		return out[i].ID < out[j].ID
	})
	c.JSON(http.StatusOK, out)
}

type createSubscriptionRequest struct {
	ChannelID   string          `json:"channel_id" binding:"required"`
	ChannelName string          `json:"channel_name"`
	Mode        string          `json:"mode" binding:"required"`
	Filter      json.RawMessage `json:"filter" binding:"required" swaggertype:"object"`
	CreatedBy   string          `json:"created_by"`
}

// @Summary	Create a subscription
// @Id			createSubscription
// @Tags		Subscriptions
// @Accept		json
// @Produce	json
// @Param		body	body		createSubscriptionRequest	true	"Channel, mode and filter"
// @Success	201		{object}	subscription.Subscription
// @Failure	400		{object}	ErrorResponse	"Invalid mode or filter"
// @Failure	409		{object}	ErrorResponse	"Bot is not a member of the channel"
// @Failure	422		{object}	ErrorResponse	"Channel could not be validated"
// @Failure	500		{object}	ErrorResponse
// @Router		/subscriptions [post]
func (s *Server) createSubscription(c *gin.Context) {
	var req createSubscriptionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return
	}
	req.ChannelID = strings.TrimSpace(req.ChannelID)
	channelName, ok := s.validateSubscription(c, req.ChannelID, req.Mode, req.Filter)
	if !ok {
		return
	}

	sub := &subscription.Subscription{
		ChannelID:   req.ChannelID,
		ChannelName: channelName,
		Mode:        req.Mode,
		Filter:      req.Filter,
		CreatedBy:   req.CreatedBy,
	}

	created, err := s.subStore.Create(c.Request.Context(), sub)
	if err != nil {
		c.JSON(http.StatusInternalServerError, errorBody(err))
		return
	}
	c.JSON(http.StatusCreated, created)
}

type updateSubscriptionRequest struct {
	Mode   string          `json:"mode" binding:"required"`
	Filter json.RawMessage `json:"filter" binding:"required" swaggertype:"object"`
}

// @Summary	Update a subscription's mode and filter
// @Id			updateSubscription
// @Tags		Subscriptions
// @Accept		json
// @Produce	json
// @Param		id		path		string						true	"Subscription id"
// @Param		body	body		updateSubscriptionRequest	true	"Mode and filter"
// @Success	200		{object}	StatusResponse
// @Failure	400		{object}	ErrorResponse	"Invalid mode or filter"
// @Failure	404		{object}	ErrorResponse
// @Failure	409		{object}	ErrorResponse	"Bot is not a member of the channel"
// @Failure	422		{object}	ErrorResponse	"Channel could not be validated"
// @Failure	500		{object}	ErrorResponse
// @Router		/subscriptions/{id} [put]
func (s *Server) updateSubscription(c *gin.Context) {
	var req updateSubscriptionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return
	}
	sub, err := s.subStore.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, errorMessage("subscription not found"))
		return
	}
	channelName, ok := s.validateSubscription(c, sub.ChannelID, req.Mode, req.Filter)
	if !ok {
		return
	}

	if err := s.subStore.Update(c.Request.Context(), c.Param("id"), channelName, req.Mode, req.Filter); err != nil {
		c.JSON(http.StatusInternalServerError, errorBody(err))
		return
	}
	c.JSON(http.StatusOK, okStatus())
}

func (s *Server) validateSubscription(c *gin.Context, channelID, mode string, filter json.RawMessage) (string, bool) {
	if channelID == "" {
		c.JSON(http.StatusBadRequest, errorMessage("channel_id is required"))
		return "", false
	}
	if err := subscription.ValidateMode(mode); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return "", false
	}
	if err := subscription.ValidateFilter(mode, filter); err != nil {
		c.JSON(http.StatusBadRequest, errorBody(err))
		return "", false
	}

	channel, err := s.im.InspectChannel(c.Request.Context(), channelID)
	if err != nil {
		c.JSON(http.StatusUnprocessableEntity, errorMessage("cannot validate channel: "+err.Error()))
		return "", false
	}
	if !channel.BotIsInChannel {
		c.JSON(http.StatusConflict, errorMessage("alertstore bot is not a member of this channel"))
		return "", false
	}
	return channel.Name, true
}

// @Summary	Delete a subscription
// @Id			deleteSubscription
// @Tags		Subscriptions
// @Produce	json
// @Param		id	path		string	true	"Subscription id"
// @Success	200	{object}	StatusResponse
// @Failure	500	{object}	ErrorResponse
// @Router		/subscriptions/{id} [delete]
func (s *Server) deleteSubscription(c *gin.Context) {
	if err := s.subStore.Delete(c.Request.Context(), c.Param("id")); err != nil {
		c.JSON(http.StatusInternalServerError, errorBody(err))
		return
	}
	c.JSON(http.StatusOK, okStatus())
}
