package knowledgeqa

import (
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/knowledgeqa/protocol"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

func normalizeCreateSession(params CreateSessionParams) CreateSessionParams {
	params.ID = strings.TrimSpace(params.ID)
	params.WorkspaceID = strings.TrimSpace(params.WorkspaceID)
	params.OrganizationID = strings.TrimSpace(params.OrganizationID)
	params.OwnerUserID = strings.TrimSpace(params.OwnerUserID)
	params.OwnerMemberID = strings.TrimSpace(params.OwnerMemberID)
	params.RootMessageID = strings.TrimSpace(params.RootMessageID)
	params.ThreadChannelID = strings.TrimSpace(params.ThreadChannelID)
	params.RequestedModel = strings.TrimSpace(params.RequestedModel)
	params.RequestedReasonLevel = strings.TrimSpace(params.RequestedReasonLevel)
	params.RequestedKnowledgeBaseIDs = normalizeStringSet(params.RequestedKnowledgeBaseIDs)
	params.RequestedSourceIDs = normalizeStringSet(params.RequestedSourceIDs)
	params.Now = normalizeNow(params.Now)
	return params
}

func validateCreateSession(params CreateSessionParams) error {
	if params.WorkspaceID == "" || params.OrganizationID == "" ||
		params.OwnerUserID == "" || params.OwnerMemberID == "" ||
		params.RootMessageID == "" || params.ThreadChannelID == "" ||
		params.RequestedModel == "" || params.RequestedReasonLevel == "" {
		return fmt.Errorf("workspace, organization, owner, root message, thread, model, and reason level are required")
	}
	if _, _, err := protocol.NormalizeSelection(params.RequestedModel, params.RequestedReasonLevel); err != nil {
		return err
	}
	return nil
}

func normalizeCreateTurn(params CreateTurnParams) CreateTurnParams {
	params.ID = strings.TrimSpace(params.ID)
	params.AttemptID = strings.TrimSpace(params.AttemptID)
	params.WorkspaceID = strings.TrimSpace(params.WorkspaceID)
	params.OrganizationID = strings.TrimSpace(params.OrganizationID)
	params.SessionID = strings.TrimSpace(params.SessionID)
	params.RequesterUserID = strings.TrimSpace(params.RequesterUserID)
	params.UserMessageID = strings.TrimSpace(params.UserMessageID)
	params.Question = strings.TrimSpace(params.Question)
	params.IdempotencyKey = strings.TrimSpace(params.IdempotencyKey)
	if params.RequestedKnowledgeBaseIDs != nil {
		params.RequestedKnowledgeBaseIDs = normalizeStringSet(params.RequestedKnowledgeBaseIDs)
	}
	if params.RequestedSourceIDs != nil {
		params.RequestedSourceIDs = normalizeStringSet(params.RequestedSourceIDs)
	}
	params.Now = normalizeNow(params.Now)
	return params
}

func validateCreateTurn(params CreateTurnParams) error {
	if params.WorkspaceID == "" || params.OrganizationID == "" ||
		params.SessionID == "" || params.RequesterUserID == "" ||
		params.UserMessageID == "" || params.Question == "" ||
		params.IdempotencyKey == "" {
		return fmt.Errorf("workspace, organization, session, requester, user message, question, and idempotency key are required")
	}
	if len(params.IdempotencyKey) > 128 {
		return fmt.Errorf("Knowledge Q&A idempotency key is too long")
	}
	return nil
}

func normalizeRetryTurn(params RetryTurnParams) RetryTurnParams {
	params.WorkspaceID = strings.TrimSpace(params.WorkspaceID)
	params.SessionID = strings.TrimSpace(params.SessionID)
	params.TurnID = strings.TrimSpace(params.TurnID)
	params.RequesterUserID = strings.TrimSpace(params.RequesterUserID)
	params.IdempotencyKey = strings.TrimSpace(params.IdempotencyKey)
	params.AttemptID = strings.TrimSpace(params.AttemptID)
	params.Now = normalizeNow(params.Now)
	return params
}

func normalizeTurnSnapshot(snapshot TurnSnapshot) TurnSnapshot {
	snapshot.AuthorizedRoots = normalizeAuthorizedRoots(snapshot.AuthorizedRoots)
	snapshot.EffectiveModel = strings.TrimSpace(snapshot.EffectiveModel)
	snapshot.EffectiveReasonLevel = strings.TrimSpace(snapshot.EffectiveReasonLevel)
	snapshot.RuntimeVersion = strings.TrimSpace(snapshot.RuntimeVersion)
	snapshot.PromptVersion = strings.TrimSpace(snapshot.PromptVersion)
	return snapshot
}

func normalizeAuthorizedRoots(roots []AuthorizedRoot) []AuthorizedRoot {
	result := make([]AuthorizedRoot, 0, len(roots))
	seen := make(map[string]struct{}, len(roots))
	for _, root := range roots {
		root.ID = strings.TrimSpace(root.ID)
		root.KnowledgeBaseID = strings.TrimSpace(root.KnowledgeBaseID)
		root.SourceID = strings.TrimSpace(root.SourceID)
		root.RootHandleRef = strings.TrimSpace(root.RootHandleRef)
		root.RootKind = strings.TrimSpace(root.RootKind)
		root.RepoRole = strings.TrimSpace(root.RepoRole)
		root.Revision = strings.TrimSpace(root.Revision)
		root.FreshnessStatus = strings.TrimSpace(root.FreshnessStatus)
		if root.ID == "" || root.KnowledgeBaseID == "" ||
			root.RootHandleRef == "" || root.Revision == "" {
			continue
		}
		if _, ok := seen[root.RootHandleRef]; ok {
			continue
		}
		seen[root.RootHandleRef] = struct{}{}
		result = append(result, root)
	}
	return result
}

func normalizeCitation(citation Citation) Citation {
	citation.ID = strings.TrimSpace(citation.ID)
	citation.WorkspaceID = strings.TrimSpace(citation.WorkspaceID)
	citation.TurnID = strings.TrimSpace(citation.TurnID)
	citation.AttemptID = strings.TrimSpace(citation.AttemptID)
	citation.CitationKind = strings.TrimSpace(citation.CitationKind)
	if citation.CitationKind == "" {
		citation.CitationKind = CitationKindKnowledge
	}
	citation.KnowledgeBaseID = strings.TrimSpace(citation.KnowledgeBaseID)
	citation.SourceID = strings.TrimSpace(citation.SourceID)
	citation.RootKind = strings.TrimSpace(citation.RootKind)
	citation.RepoRole = strings.TrimSpace(citation.RepoRole)
	citation.Revision = strings.TrimSpace(citation.Revision)
	citation.ResolvedRef = strings.TrimSpace(citation.ResolvedRef)
	citation.ResolvedRefKind = strings.TrimSpace(citation.ResolvedRefKind)
	citation.Path = strings.TrimSpace(citation.Path)
	citation.URL = strings.TrimSpace(citation.URL)
	citation.Title = strings.TrimSpace(citation.Title)
	citation.Snippet = strings.TrimSpace(citation.Snippet)
	if citation.FetchedAt != nil {
		fetchedAt := citation.FetchedAt.UTC()
		citation.FetchedAt = &fetchedAt
	}
	return citation
}

func validTurnTransition(from, to string) bool {
	switch strings.TrimSpace(from) + ">" + strings.TrimSpace(to) {
	case "queued>preparing",
		"preparing>generating":
		return true
	default:
		return false
	}
}

func isTerminalTurnStatus(status string) bool {
	switch strings.TrimSpace(status) {
	case "completed", "no_answer", "failed", "canceled":
		return true
	default:
		return false
	}
}

func normalizeNow(now time.Time) time.Time {
	if now.IsZero() {
		return time.Now().UTC()
	}
	return now.UTC()
}

func normalizeStringSet(values []string) []string {
	seen := make(map[string]struct{}, len(values))
	result := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}

func decodeStringList(raw []byte, target *[]string) error {
	if len(raw) == 0 {
		*target = []string{}
		return nil
	}
	if err := model.UnmarshalJSONObject("Knowledge Q&A string list", raw, target); err != nil {
		return err
	}
	*target = normalizeStringSet(*target)
	return nil
}

func nonNilMap(value map[string]any) map[string]any {
	if value == nil {
		return map[string]any{}
	}
	return value
}
