package auth

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/service/authflow"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

// InviteHandler owns invite discovery and acceptance routes.
type InviteHandler struct {
	authFlow AuthFlow
	realtime Realtime
}

// Info handles GET /api/v1/auth/invite-info.
// Returns details about a workspace or organization invite by token.
func (handler *InviteHandler) Info(ctx *gin.Context) {
	token := strings.TrimSpace(ctx.Query("token"))
	if token == "" {
		api.Error(ctx, http.StatusBadRequest, "token is required")
		return
	}
	if handler.authFlow == nil {
		api.Error(ctx, http.StatusBadRequest, "invalid invite token")
		return
	}

	info, found, err := handler.authFlow.FindInviteInfoByToken(ctx.Request.Context(), token)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if !found {
		api.Error(ctx, http.StatusBadRequest, "invalid invite token")
		return
	}

	switch info.Kind {
	case authflow.InviteKindWorkspace:
		ctx.JSON(http.StatusOK, workspaceInviteInfoPayload(info.Workspace))
	case authflow.InviteKindOrganization:
		ctx.JSON(http.StatusOK, gin.H{
			"kind":                 "organization",
			"organizationId":       info.Organization.OrganizationID,
			"organizationName":     info.Organization.OrganizationName,
			"inviterName":          info.Organization.InviterName,
			"invitedEmail":         info.Organization.InvitedEmail,
			"role":                 info.Organization.Role,
			"teamId":               info.Organization.TeamID,
			"teamName":             info.Organization.TeamName,
			"defaultWorkspaceRole": info.Organization.DefaultWorkspaceRole,
		})
	default:
		api.Error(ctx, http.StatusBadRequest, "invalid invite token")
	}
}

// PendingInfo handles GET /api/v1/auth/pending-invites/:inviteId.
// Returns details for an email- or mobile-bound workspace invite by stable invite id.
func (handler *InviteHandler) PendingInfo(ctx *gin.Context) {
	inviteID := strings.TrimSpace(ctx.Param("inviteId"))
	if inviteID == "" {
		api.Error(ctx, http.StatusBadRequest, "inviteId is required")
		return
	}
	if handler.authFlow == nil {
		api.Error(ctx, http.StatusBadRequest, "invalid invite")
		return
	}

	info, found, err := handler.authFlow.FindWorkspaceInviteInfoByID(ctx.Request.Context(), inviteID)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if !found || info.Kind != authflow.InviteKindWorkspace {
		api.Error(ctx, http.StatusBadRequest, "invalid invite")
		return
	}

	ctx.JSON(http.StatusOK, workspaceInviteInfoPayload(info.Workspace))
}

// Pending handles GET /api/v1/auth/pending-invites.
// It returns invites addressed to the authenticated user's verified identity.
func (handler *InviteHandler) Pending(r api.AuthenticatedRequest) (api.Response, error) {
	if handler.authFlow == nil || !r.User.User.HasVerifiedIdentity() {
		return api.OK(gin.H{"invites": []gin.H{}}), nil
	}
	pending, err := handler.authFlow.ListPendingInvitesForUser(r.Context(), r.User.User)
	if err != nil {
		return api.Response{}, err
	}
	invites := make([]gin.H, 0, len(pending))
	for _, invite := range pending {
		switch invite.Kind {
		case authflow.InviteKindWorkspace:
			invites = append(invites, pendingWorkspaceInvitePayload(invite.Workspace))
		}
	}
	return api.OK(gin.H{"invites": invites}), nil
}

func workspaceInviteInfoPayload(info workspace.WorkspaceInviteInfo) gin.H {
	payload := gin.H{
		"kind":          "workspace",
		"workspaceId":   info.WorkspaceID,
		"workspaceName": info.WorkspaceName,
		"inviterName":   info.InviterName,
	}
	addInviteTargetFields(payload, info.InvitedEmail, info.InvitedMobileHint)
	return payload
}

func pendingWorkspaceInvitePayload(invite workspace.PendingWorkspaceInvite) gin.H {
	payload := gin.H{
		"kind":          "workspace",
		"inviteId":      invite.ID,
		"workspaceId":   invite.WorkspaceID,
		"workspaceName": invite.WorkspaceName,
		"inviterName":   invite.InviterName,
		"createdAt":     invite.CreatedAt,
		"expiresAt":     invite.ExpiresAt,
	}
	addInviteTargetFields(payload, invite.InvitedEmail, invite.InvitedMobileHint)
	return payload
}

func addInviteTargetFields(payload gin.H, invitedEmail string, invitedMobileHint string) {
	if email := strings.TrimSpace(invitedEmail); email != "" {
		payload["invitedEmail"] = email
	}
	if mobileHint := strings.TrimSpace(invitedMobileHint); mobileHint != "" {
		payload["invitedMobileHint"] = mobileHint
	}
}

// AcceptPending handles POST /api/v1/auth/pending-invites/:inviteId/accept.
// It accepts an invite addressed to the authenticated user's verified identity.
func (handler *InviteHandler) AcceptPending(r api.AuthenticatedRequest) (api.Response, error) {
	inviteID := strings.TrimSpace(r.Gin.Param("inviteId"))
	if inviteID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "inviteId is required")
	}
	if !r.User.User.HasVerifiedIdentity() {
		return api.Response{}, api.NewHTTPError(http.StatusForbidden, "Email or mobile must be verified to accept this invite")
	}
	if handler.authFlow == nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite")
	}

	accepted, found, err := handler.authFlow.AcceptPendingInviteByID(r.Context(), r.User.User, inviteID)
	if err != nil {
		return api.Response{}, err
	}
	if !found {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite")
	}

	switch accepted.Kind {
	case authflow.InviteKindWorkspace:
		handler.emitWorkspaceMembershipUpdated(accepted.Workspace)
		return api.OK(gin.H{
			"kind":          "workspace",
			"workspaceId":   accepted.Workspace.WorkspaceID,
			"workspaceName": accepted.Workspace.Name,
		}), nil
	default:
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite")
	}
}

// DismissPending handles POST /api/v1/auth/pending-invites/:inviteId/dismiss.
// It hides an invite addressed to the authenticated user's verified identity.
func (handler *InviteHandler) DismissPending(r api.AuthenticatedRequest) (api.Response, error) {
	inviteID := strings.TrimSpace(r.Gin.Param("inviteId"))
	if inviteID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "inviteId is required")
	}
	if !r.User.User.HasVerifiedIdentity() {
		return api.Response{}, api.NewHTTPError(http.StatusForbidden, "Email or mobile must be verified to dismiss this invite")
	}
	if handler.authFlow == nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite")
	}

	found, err := handler.authFlow.DismissPendingInviteByID(r.Context(), r.User.User, inviteID)
	if err != nil {
		return api.Response{}, err
	}
	if !found {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite")
	}
	return api.OK(gin.H{}), nil
}

// Accept handles POST /api/v1/auth/accept-invite.
// Accepts a workspace or organization invite for the authenticated user.
func (handler *InviteHandler) Accept(r api.AuthenticatedJSON[dto.AcceptInviteRequest]) (api.Response, error) {

	request := r.Body
	token := strings.TrimSpace(request.Token)
	if token == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "token is required")
	}
	if handler.authFlow == nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite token")
	}

	accepted, found, err := handler.authFlow.AcceptInviteByToken(r.Context(), r.User.User, token)
	if err != nil {
		return api.Response{}, err
	}
	if !found {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite token")
	}

	switch accepted.Kind {
	case authflow.InviteKindWorkspace:
		handler.emitWorkspaceMembershipUpdated(accepted.Workspace)
		return api.OK(gin.H{
			"kind":          "workspace",
			"workspaceId":   accepted.Workspace.WorkspaceID,
			"workspaceName": accepted.Workspace.Name,
		}), nil
	case authflow.InviteKindOrganization:
		return api.OK(gin.H{
			"kind":             "organization",
			"organizationId":   accepted.Organization.OrganizationID,
			"organizationName": accepted.Organization.OrganizationName,
			"role":             accepted.Organization.Role,
		}), nil
	default:
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid invite token")
	}
}

func (handler *InviteHandler) emitWorkspaceMembershipUpdated(summary workspace.Summary) {
	if handler.realtime == nil {
		return
	}
	workspaceID := strings.TrimSpace(summary.WorkspaceID)
	if workspaceID == "" {
		return
	}
	handler.realtime.EmitWorkspaceMembersUpdated(workspaceID)
	if channelID := strings.TrimSpace(summary.AllChannelID); channelID != "" {
		if memberUserID := strings.TrimSpace(summary.MemberUserID); memberUserID != "" {
			handler.realtime.SubscribeScopeForUser(workspaceID, memberUserID, channelID)
		}
		handler.realtime.EmitChannelMembersUpdated(workspaceID, channelID)
	}
}
