package channel

import (
	"context"
	"errors"
	"net/http"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	storechannel "github.com/tidbcloud/AgentTeam/internal/store/channel"
)

// ChannelPermissionsStore is the store surface for the channel-permission routes.
type ChannelPermissionsStore interface {
	ListChannelPermissions(ctx context.Context, workspaceID string, channelID string) ([]storechannel.ChannelPermission, error)
	InsertChannelPermission(ctx context.Context, workspaceID string, channelID string, memberID string, userID string, displayName string, createdBy string) (storechannel.ChannelPermission, error)
	DeleteChannelPermission(ctx context.Context, workspaceID string, channelID string, memberID string) error
	IsChannelPermissionHolderForUser(ctx context.Context, userID string, workspaceID string, channelID string) (bool, error)
}

// ChannelPermissionsHandler owns the channel-permission grant/revoke/list routes.
// Authorization:
//   - List requires channel read access (the caller is a joined member).
//   - Grant/Revoke require the caller to be the channel owner or already hold
//     the channel permission.
type ChannelPermissionsHandler struct {
	store    ChannelPermissionsStore
	channels ChannelAccessStore
}

// List handles GET /api/v1/workspaces/:workspaceId/channels/:channelId/permissions.
func (h *ChannelPermissionsHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	workspaceID := r.WorkspaceID()
	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}
	if err := h.requireChannelRead(r.Context(), r.User.User.ID, workspaceID, channelID); err != nil {
		return api.Response{}, err
	}
	permissions, err := h.store.ListChannelPermissions(r.Context(), workspaceID, channelID)
	if err != nil {
		return api.Response{}, err
	}
	out := make([]dto.ChannelPermissionResponse, 0, len(permissions))
	for _, p := range permissions {
		out = append(out, dto.ChannelPermissionResponse{
			ID:          p.ID,
			WorkspaceID: p.WorkspaceID,
			ChannelID:   p.ChannelID,
			MemberID:    p.MemberID,
			UserID:      p.UserID,
			DisplayName: p.DisplayName,
			CreatedBy:   p.CreatedBy,
			CreatedAt:   p.CreatedAt,
		})
	}
	return api.OK(out), nil
}

// Grant handles POST /api/v1/workspaces/:workspaceId/channels/:channelId/permissions.
func (h *ChannelPermissionsHandler) Grant(r api.WorkspaceScopedJSON[dto.ChannelPermissionRequest]) (api.Response, error) {
	workspaceID := r.WorkspaceID()
	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}
	body := r.Body
	if strings.TrimSpace(body.MemberID) == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "memberId is required")
	}
	if strings.TrimSpace(body.UserID) == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "userId is required")
	}
	if err := h.requireChannelMutate(r.Context(), r.User.User.ID, workspaceID, channelID); err != nil {
		return api.Response{}, err
	}
	_, err := h.store.InsertChannelPermission(r.Context(), workspaceID, channelID, body.MemberID, body.UserID, body.DisplayName, r.User.User.ID)
	if err != nil {
		if errors.Is(err, storechannel.ErrChannelPermissionAlreadyGranted) {
			return api.Response{}, api.NewHTTPError(http.StatusConflict, "member already holds the channel permission")
		}
		return api.Response{}, err
	}
	return api.OK(dto.EmptyResponse{}), nil
}

// Revoke handles DELETE /api/v1/workspaces/:workspaceId/channels/:channelId/permissions/:memberId.
func (h *ChannelPermissionsHandler) Revoke(r api.WorkspaceScopedRequest) (api.Response, error) {
	workspaceID := r.WorkspaceID()
	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	memberID := strings.TrimSpace(r.Gin.Param("memberId"))
	if channelID == "" || memberID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId and memberId are required")
	}
	if err := h.requireChannelMutate(r.Context(), r.User.User.ID, workspaceID, channelID); err != nil {
		return api.Response{}, err
	}
	if err := h.store.DeleteChannelPermission(r.Context(), workspaceID, channelID, memberID); err != nil {
		if errors.Is(err, storechannel.ErrChannelPermissionNotGranted) {
			return api.Response{}, api.NewHTTPError(http.StatusNotFound, "member does not hold the channel permission")
		}
		return api.Response{}, err
	}
	return api.OK(dto.EmptyResponse{}), nil
}

// requireChannelRead authorizes the caller to read a channel's permission
// list. Today the gate is "the caller is a joined member of the channel",
// matching the rest of the channel route surface.
func (h *ChannelPermissionsHandler) requireChannelRead(ctx context.Context, userID string, workspaceID string, channelID string) error {
	if h.channels == nil {
		return api.NewHTTPError(http.StatusForbidden, "forbidden")
	}
	resolution, err := h.channels.ResolveChannelAccessForUser(ctx, userID, workspaceID, channelID)
	if err != nil {
		return err
	}
	if resolution.Status != storechannel.ChannelAccessStatusJoined {
		return api.NewHTTPError(http.StatusForbidden, "forbidden")
	}
	return nil
}

// requireChannelMutate authorizes the caller to grant or revoke a channel
// permission. The gate is "the caller is the channel owner or already holds
// the channel permission".
func (h *ChannelPermissionsHandler) requireChannelMutate(ctx context.Context, userID string, workspaceID string, channelID string) error {
	if h.channels == nil {
		return api.NewHTTPError(http.StatusForbidden, "forbidden")
	}
	resolution, err := h.channels.ResolveChannelAccessForUser(ctx, userID, workspaceID, channelID)
	if err != nil {
		return err
	}
	if resolution.Status != storechannel.ChannelAccessStatusJoined {
		return api.NewHTTPError(http.StatusForbidden, "forbidden")
	}
	if resolution.Channel.CanManage {
		return nil
	}
	holds, err := h.store.IsChannelPermissionHolderForUser(ctx, userID, workspaceID, channelID)
	if err != nil {
		return err
	}
	if !holds {
		return api.NewHTTPError(http.StatusForbidden, "forbidden")
	}
	return nil
}
