package agent

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/service/approvalapp"
	"github.com/tidbcloud/AgentTeam/internal/service/team/roleproposal"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
)

const (
	defaultAgentTeamListLimit = 20
	maxAgentTeamListLimit     = 100
)

type agentTeamListCursor struct {
	Version     int    `json:"v"`
	WorkspaceID string `json:"workspaceId"`
	Query       string `json:"query"`
	DisplayName string `json:"displayName"`
	Handle      string `json:"handle"`
	TeamID      string `json:"teamId"`
}

// ApprovalRuntime is the narrow internal boundary used by Team update to
// submit and present its approval. Approval is not a model-facing Exec action.
type ApprovalRuntime interface {
	SubmitRequest(context.Context, string, approvalapp.SubmitCommand) (approvalapp.SubmitResult, error)
	PresentPending(context.Context, approvalapp.SubmitResult) (app.AppNotificationResult, error)
}

type AgentTeamHandler struct {
	teams     AgentTeamStore
	approvals ApprovalRuntime
}

type agentTeamListResponse struct {
	Teams      []agentTeamSummary `json:"teams"`
	HasMore    bool               `json:"hasMore"`
	NextCursor string             `json:"nextCursor,omitempty"`
}

type agentTeamSummary struct {
	TeamID      string `json:"teamId"`
	Name        string `json:"name"`
	Handle      string `json:"handle"`
	Description string `json:"description"`
	Role        string `json:"role"`
}

type agentTeamGetRequest struct {
	TeamID string `json:"teamId"`
}

type agentTeamGetResponse struct {
	Team agentTeamDetail `json:"team"`
}

type agentTeamDetail struct {
	TeamID      string            `json:"teamId"`
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Members     []agentTeamMember `json:"members"`
}

type agentTeamMember struct {
	TeamMemberID      string `json:"teamMemberId"`
	AgentID           string `json:"agentId"`
	DisplayName       string `json:"displayName"`
	RoleLabel         string `json:"roleLabel"`
	RoleOverlayPrompt string `json:"roleOverlayPrompt"`
	WakeOnTeamMention bool   `json:"wakeOnTeamMention"`
}

type agentTeamUpdateRequest struct {
	TeamID        string                  `json:"teamId"`
	RequestID     string                  `json:"requestId"`
	Description   *string                 `json:"description,omitempty"`
	MemberUpdates []agentTeamMemberUpdate `json:"memberUpdates,omitempty"`
}

type agentTeamMemberUpdate struct {
	TeamMemberID      string  `json:"teamMemberId"`
	RoleLabel         *string `json:"roleLabel,omitempty"`
	RoleOverlayPrompt *string `json:"roleOverlayPrompt,omitempty"`
	WakeOnTeamMention *bool   `json:"wakeOnTeamMention,omitempty"`
}

type agentTeamUpdateResponse struct {
	Status string `json:"status"`
}

func (handler *AgentTeamHandler) List(request api.AgentScopedRequest) (api.Response, error) {
	if handler == nil || handler.teams == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Team store is not configured")
	}
	limit, err := parseAgentTeamLimit(request.Gin.Query("limit"))
	if err != nil {
		return api.Response{}, err
	}
	query := strings.ToLower(strings.TrimSpace(request.Gin.Query("q")))
	if query == "" {
		query = strings.ToLower(strings.TrimSpace(request.Gin.Query("query")))
	}
	options := AgentTeamListOptions{Query: query, Limit: limit}
	rawCursor := strings.TrimSpace(request.Gin.Query("cursor"))
	if rawCursor != "" {
		var cursor agentTeamListCursor
		if err := decodeAgentCollectionCursor(rawCursor, "team", &cursor); err != nil || cursor.Version != 1 || cursor.WorkspaceID != request.Principal.WorkspaceID || cursor.Query != query || strings.TrimSpace(cursor.Handle) == "" || strings.TrimSpace(cursor.TeamID) == "" {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "invalid team cursor")
		}
		options.CursorName = cursor.DisplayName
		options.CursorHandle = cursor.Handle
		options.CursorTeamID = strings.TrimSpace(cursor.TeamID)
	}
	page, err := handler.teams.ListTeamsForAgent(request.Context(), request.Principal, options)
	if err != nil {
		return api.Response{}, err
	}
	response := make([]agentTeamSummary, 0, len(page.Teams))
	for _, item := range page.Teams {
		response = append(response, agentTeamSummary{
			TeamID: item.TeamID, Name: firstNonEmpty(item.DisplayName, item.Handle), Handle: item.Handle,
			Description: item.Description, Role: item.Role,
		})
	}
	nextCursor := ""
	if page.HasMore && len(page.Teams) > 0 {
		last := page.Teams[len(page.Teams)-1]
		nextCursor, err = encodeAgentCollectionCursor(agentTeamListCursor{
			Version: 1, WorkspaceID: request.Principal.WorkspaceID, Query: query, DisplayName: last.DisplayName, Handle: last.Handle, TeamID: last.TeamID,
		})
		if err != nil {
			return api.Response{}, err
		}
	}
	return api.OK(agentTeamListResponse{Teams: response, HasMore: page.HasMore, NextCursor: nextCursor}), nil
}

func (handler *AgentTeamHandler) Get(request api.AgentScopedJSON[agentTeamGetRequest]) (api.Response, error) {
	if handler == nil || handler.teams == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Team store is not configured")
	}
	teamID := strings.TrimSpace(request.Body.TeamID)
	if teamID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "teamId is required")
	}
	loaded, err := handler.teams.GetTeamForAgent(request.Context(), request.Principal, teamID)
	if err != nil {
		return api.Response{}, err
	}
	memberResponses := make([]agentTeamMember, 0, len(loaded.Members))
	for _, member := range loaded.Members {
		memberResponses = append(memberResponses, agentTeamMember{
			TeamMemberID: member.TeamMemberID, AgentID: member.AgentID, DisplayName: member.DisplayName,
			RoleLabel: member.RoleLabel, RoleOverlayPrompt: member.RoleOverlayPrompt,
			WakeOnTeamMention: member.WakeOnTeamMention,
		})
	}
	return api.OK(agentTeamGetResponse{Team: agentTeamDetail{
		TeamID: loaded.TeamID, Name: firstNonEmpty(loaded.DisplayName, loaded.Handle),
		Description: loaded.Description, Members: memberResponses,
	}}), nil
}

func (handler *AgentTeamHandler) Update(request api.AgentScopedJSON[agentTeamUpdateRequest]) (api.Response, error) {
	if handler == nil || handler.approvals == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Approval runtime is not configured")
	}
	body := request.Body
	body.TeamID = strings.TrimSpace(body.TeamID)
	body.RequestID = strings.TrimSpace(body.RequestID)
	if body.TeamID == "" || body.RequestID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "teamId and requestId are required")
	}
	if body.Description == nil && len(body.MemberUpdates) == 0 {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "description or memberUpdates is required")
	}
	proposal := roleproposal.SubmitInput{Description: trimOptionalTeamString(body.Description)}
	proposal.Members = make([]roleproposal.MemberInput, 0, len(body.MemberUpdates))
	for _, member := range body.MemberUpdates {
		member.TeamMemberID = strings.TrimSpace(member.TeamMemberID)
		if member.TeamMemberID == "" || (member.RoleLabel == nil && member.RoleOverlayPrompt == nil && member.WakeOnTeamMention == nil) {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "each member update requires teamMemberId and one editable field")
		}
		proposal.Members = append(proposal.Members, roleproposal.MemberInput{
			TeamMemberID: member.TeamMemberID, RoleLabel: member.RoleLabel,
			RoleOverlayPrompt: trimOptionalTeamString(member.RoleOverlayPrompt),
			WakeOnTeamMention: member.WakeOnTeamMention,
		})
	}
	result, err := handler.approvals.SubmitRequest(request.Context(), "team_role", approvalapp.SubmitCommand{
		Principal: approvalapp.SubjectPrincipal{
			ActorType: "agent", WorkspaceID: request.Principal.WorkspaceID,
			PrincipalID: request.Principal.AgentID, MemberID: request.Principal.MemberID,
			MachineID: request.Principal.MachineID,
		},
		SubjectRef: body.TeamID, ClientRequestID: body.RequestID, Payload: proposal,
		CorrelationID: strings.TrimSpace(request.Gin.GetHeader("X-Request-ID")),
	})
	if err != nil {
		return api.Response{}, err
	}
	if result.Instance.State == "pending" {
		if _, err := handler.approvals.PresentPending(request.Context(), result); err != nil {
			return api.Response{}, err
		}
	}
	return api.OK(agentTeamUpdateResponse{Status: result.Instance.State}), nil
}

func trimOptionalTeamString(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	return &trimmed
}

func parseAgentTeamLimit(raw string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return defaultAgentTeamListLimit, nil
	}
	limit, err := strconv.Atoi(raw)
	if err != nil || limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > maxAgentTeamListLimit {
		return maxAgentTeamListLimit, nil
	}
	return limit, nil
}
