// Package channel contains browser-facing channel, DM, and thread routes.
package channel

import (
	"log/slog"
	"net/http"

	"github.com/gin-gonic/gin"

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

func channelHandlerOptions(opts []api.HandlerOption) []api.HandlerOption {
	options := make([]api.HandlerOption, 0, len(opts)+1)
	options = append(options, api.WithErrorClassifier(classifyChannelError))
	options = append(options, opts...)
	return options
}

func classifyChannelError(err error) (api.ErrorResponse, bool) {
	if storechannel.IsChannelSlugConflict(err) {
		return api.ErrorResponse{
			Status:  http.StatusConflict,
			Message: storechannel.ChannelSlugConflictMessage,
			Code:    storechannel.ChannelSlugConflictCode,
		}, true
	}
	return api.ErrorResponse{}, false
}

func handleChannelWorkspace(handler api.WorkspaceScopedHandler, opts ...api.HandlerOption) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(handler, channelHandlerOptions(opts)...)
}

func handleChannelWorkspaceJSON[T any](
	handler func(api.WorkspaceScopedJSON[T]) (api.Response, error),
	opts ...api.HandlerOption,
) gin.HandlerFunc {
	return api.HandleWorkspaceScopedJSON[T](handler, channelHandlerOptions(opts)...)
}

// Dependencies contains collaborators and config for channel routes.
type Dependencies struct {
	AuthStore                 api.SessionAuthenticator
	WorkspaceStore            api.WorkspaceResolver
	Channels                  ChannelStore
	AppStore                  ChannelAppStore
	ChannelPermissions        ChannelPermissionsStore
	ThreadStore               ThreadStore
	ThreadSubsystemChannels   ThreadSubsystemChannelStore
	ThreadReviewStore         ThreadReviewStore
	EmailVerificationRequired bool
	Realtime                  ChannelRealtime
	UnreadRuntime             ChannelUnreadReader
	MessageFanout             MessageFanout
	Hub                       ThreadReviewHub
	Logger                    *slog.Logger
}

// RegisterRoutes mounts browser-facing channel and thread routes.
func RegisterRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	apiGroup := router.Group("/api")
	workspaceScoped := apiGroup.Group(
		"/v1/workspaces/:workspaceId",
		api.RequireAuthenticatedUser(dependencies.AuthStore),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
		api.RequireRouteWorkspace(dependencies.WorkspaceStore),
	)
	registerChannelRoutes(workspaceScoped, dependencies)
	registerThreadRoutes(workspaceScoped, dependencies)
}

func registerChannelRoutes(workspaceScoped gin.IRoutes, dependencies Dependencies) {
	crud := &ChannelCRUDHandler{
		channels:      dependencies.Channels,
		appStore:      dependencies.AppStore,
		realtime:      dependencies.Realtime,
		unreadRuntime: dependencies.UnreadRuntime,
	}
	lifecycle := &ChannelLifecycleHandler{
		appStore:      dependencies.AppStore,
		realtime:      dependencies.Realtime,
		unreadRuntime: dependencies.UnreadRuntime,
	}
	membership := &ChannelMembershipHandler{
		channels:      dependencies.Channels,
		appStore:      dependencies.AppStore,
		realtime:      dependencies.Realtime,
		messageFanout: dependencies.MessageFanout,
		unreadRuntime: dependencies.UnreadRuntime,
		logger:        dependencies.Logger,
	}
	discovery := &ChannelDiscoveryHandler{
		channels: dependencies.Channels,
	}
	access := &ChannelAccessHandler{
		channels: dependencies.Channels,
	}
	dm := &ChannelDMHandler{
		appStore:      dependencies.AppStore,
		realtime:      dependencies.Realtime,
		unreadRuntime: dependencies.UnreadRuntime,
	}
	attachments := &ChannelAttachmentsHandler{
		appStore: dependencies.AppStore,
	}
	shareCards := &ChannelShareCardHandler{
		appStore:      dependencies.AppStore,
		realtime:      dependencies.Realtime,
		messageFanout: dependencies.MessageFanout,
		unreadRuntime: dependencies.UnreadRuntime,
	}
	permissions := &ChannelPermissionsHandler{
		store:    dependencies.ChannelPermissions,
		channels: dependencies.Channels,
	}
	workspaceScoped.GET("/channels", handleChannelWorkspace(crud.List))
	workspaceScoped.POST("/channels", handleChannelWorkspaceJSON[dto.ChannelCreateRequest](crud.Create))
	workspaceScoped.GET("/channels/discover", handleChannelWorkspace(discovery.DiscoverList))
	workspaceScoped.GET("/channels/discover/:channelId", handleChannelWorkspace(discovery.DiscoverGet))
	workspaceScoped.GET("/channels/:channelId/access", handleChannelWorkspace(access.Resolve))
	workspaceScoped.GET("/channels/:channelId/attachments", handleChannelWorkspace(attachments.List))
	workspaceScoped.GET("/threads/:threadChannelId/attachments", handleChannelWorkspace(attachments.ListThread))
	workspaceScoped.PATCH("/channels/:channelId", handleChannelWorkspaceJSON[dto.ChannelUpdateRequest](crud.Update))
	workspaceScoped.DELETE("/channels/:channelId", handleChannelWorkspace(crud.Delete))
	workspaceScoped.POST("/channels/:channelId/share-card", handleChannelWorkspaceJSON[dto.ChannelShareCardCreateRequest](shareCards.Create))
	workspaceScoped.POST("/channels/:channelId/owner", handleChannelWorkspaceJSON[dto.ChannelTransferOwnerRequest](lifecycle.TransferOwner))
	workspaceScoped.POST("/channels/:channelId/join", handleChannelWorkspace(membership.Join))
	workspaceScoped.POST("/channels/:channelId/archive", handleChannelWorkspace(lifecycle.Archive))
	workspaceScoped.POST("/channels/:channelId/unarchive", handleChannelWorkspace(lifecycle.Unarchive))
	workspaceScoped.POST("/channels/:channelId/leave", handleChannelWorkspace(membership.Leave))
	workspaceScoped.GET("/channels/dm", handleChannelWorkspace(dm.ListDM))
	workspaceScoped.POST("/channels/dm", handleChannelWorkspaceJSON[dto.OpenDMRequest](dm.OpenDM))
	workspaceScoped.GET("/channels/dm/:channelId", handleChannelWorkspace(dm.GetDM))
	workspaceScoped.POST("/channels/dm/:channelId/hide", handleChannelWorkspace(dm.HideDM))
	workspaceScoped.POST("/channels/:channelId/owner-context-dismiss", handleChannelWorkspaceJSON[dto.ChannelDismissOwnerContextRequest](lifecycle.DismissOwnerContext))
	workspaceScoped.GET("/channels/:channelId/addable-agents", handleChannelWorkspace(membership.AddableAgents))
	workspaceScoped.GET("/channels/:channelId/addable-people", handleChannelWorkspace(membership.AddablePeople))
	workspaceScoped.GET("/channels/:channelId/members", handleChannelWorkspace(membership.GetMembers))
	workspaceScoped.GET("/channels/:channelId/permissions", handleChannelWorkspace(permissions.List))
	workspaceScoped.POST("/channels/:channelId/permissions", handleChannelWorkspaceJSON[dto.ChannelPermissionRequest](permissions.Grant))
	workspaceScoped.DELETE("/channels/:channelId/permissions/:memberId", handleChannelWorkspace(permissions.Revoke))
	workspaceScoped.POST("/channels/:channelId/members", handleChannelWorkspaceJSON[dto.AddChannelMemberRequest](membership.AddMember))
	workspaceScoped.POST("/channels/:channelId/members/batch", handleChannelWorkspaceJSON[dto.BatchAddChannelMembersRequest](membership.BatchAddMembers))
	workspaceScoped.POST("/channels/:channelId/agent-task-summaries", handleChannelWorkspace(membership.AgentTaskSummaries))
	workspaceScoped.PATCH("/channels/:channelId/members/agent/:agentId", handleChannelWorkspaceJSON[dto.UpdateChannelAgentVisibilityRequest](membership.UpdateAgentVisibility))
	workspaceScoped.DELETE("/channels/:channelId/members/agent/:agentId", handleChannelWorkspace(membership.RemoveAgentMember))
	workspaceScoped.DELETE("/channels/:channelId/members/service/:memberId", handleChannelWorkspace(membership.RemoveServiceMember))
	workspaceScoped.DELETE("/channels/:channelId/members/user/:userId", handleChannelWorkspace(membership.RemoveUserMember))
	workspaceScoped.GET("/channel-share-cards/:messageId", handleChannelWorkspace(shareCards.Get))
	workspaceScoped.POST("/channel-share-cards/:messageId/share", handleChannelWorkspaceJSON[dto.ChannelShareCardCreateRequest](shareCards.Share))
	workspaceScoped.POST("/channel-share-cards/:messageId/join", handleChannelWorkspace(shareCards.Join))
	workspaceScoped.POST("/channel-share-cards/:messageId/revoke", handleChannelWorkspace(shareCards.Revoke))
}

func registerThreadRoutes(workspaceScoped gin.IRoutes, dependencies Dependencies) {
	handler := newThreadHandler(dependencies)
	workspaceScoped.GET("/channels/:channelId/threads", handleChannelWorkspace(handler.ListForChannelV1))
	workspaceScoped.GET("/threads/count", handleChannelWorkspace(handler.Count))
	workspaceScoped.GET("/threads", handleChannelWorkspace(handler.ListV1))
	workspaceScoped.GET("/threads/:threadChannelId/membership", handleChannelWorkspace(handler.Membership))
	workspaceScoped.GET("/threads/:threadChannelId", handleChannelWorkspace(handler.Get))
	workspaceScoped.POST("/channels/:channelId/threads", handleChannelWorkspaceJSON[dto.AppOpenThreadRequest](handler.OpenForChannel))
	workspaceScoped.POST("/threads/:threadChannelId/share-card", handleChannelWorkspaceJSON[dto.ThreadShareCardCreateRequest](handler.CreateShareCard))
	workspaceScoped.POST("/threads/:threadChannelId/subscribe", handleChannelWorkspace(handler.Subscribe))
	workspaceScoped.POST("/threads/:threadChannelId/unsubscribe", handleChannelWorkspace(handler.Unsubscribe))
	workspaceScoped.GET("/threads/:threadChannelId/review-contexts", handleChannelWorkspace(handler.ListReviewContexts))
	workspaceScoped.GET("/threads/:threadChannelId/review/snapshot", handleChannelWorkspace(handler.ReviewSnapshot))
	workspaceScoped.GET("/threads/:threadChannelId/review/fingerprint", handleChannelWorkspace(handler.ReviewFingerprint))
	workspaceScoped.GET("/threads/:threadChannelId/review/summary", handleChannelWorkspace(handler.ReviewSummary))
	workspaceScoped.GET("/threads/:threadChannelId/review/files", handleChannelWorkspace(handler.ReviewFiles))
	workspaceScoped.GET("/threads/:threadChannelId/review/diff", handleChannelWorkspace(handler.ReviewDiff))
	workspaceScoped.POST("/threads/:threadChannelId/review/diff/batch", handleChannelWorkspaceJSON[dto.ReviewDiffBatchRequest](handler.ReviewDiffBatch))
	workspaceScoped.GET("/thread-share-cards/:messageId", handleChannelWorkspace(handler.GetShareCard))
	workspaceScoped.GET("/review/feedback", handleChannelWorkspace(handler.ListReviewFeedback))
	workspaceScoped.POST("/review/feedback", handleChannelWorkspaceJSON[dto.ReviewFeedbackSubmitRequest](handler.SubmitReviewFeedback))
	workspaceScoped.PATCH("/review/feedback/:messageId/items/:itemId", handleChannelWorkspaceJSON[dto.ReviewFeedbackStatusRequest](handler.UpdateReviewFeedbackItemStatus))
	workspaceScoped.GET("/review-contexts/:reviewContextId/feedback", handleChannelWorkspace(handler.ListReviewFeedback))
	workspaceScoped.POST("/review-contexts/:reviewContextId/feedback", handleChannelWorkspaceJSON[dto.ReviewFeedbackSubmitRequest](handler.SubmitReviewFeedback))
	workspaceScoped.POST("/review-contexts/:reviewContextId/feedback/:messageId/items/:itemId/resolve", handleChannelWorkspace(handler.ResolveReviewFeedbackItem))
	workspaceScoped.POST("/review-contexts/:reviewContextId/feedback/:messageId/items/:itemId/reopen", handleChannelWorkspace(handler.ReopenReviewFeedbackItem))
}

func newThreadHandler(dependencies Dependencies) *ThreadHandler {
	return &ThreadHandler{
		store:             dependencies.ThreadStore,
		subsystemChannels: dependencies.ThreadSubsystemChannels,
		appStore:          dependencies.AppStore,
		reviewStore:       dependencies.ThreadReviewStore,
		messageFanout:     dependencies.MessageFanout,
		realtime:          dependencies.Realtime,
		hub:               dependencies.Hub,
		unreadRuntime:     dependencies.UnreadRuntime,
	}
}
