package kb

import (
	"context"
	"encoding/json"
	"strings"

	activityservice "github.com/tidbcloud/AgentTeam/internal/service/resource/activity"

	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	activitystore "github.com/tidbcloud/AgentTeam/internal/store/resource/activity"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

const kbActivityPageSize = 100

func (s *KBService) ListKnowledgeBaseActivity(ctx context.Context, workspaceID, subject string, opts activityservice.ResourceActivityListOptions) (activityservice.ResourceActivityPage, error) {
	if s == nil || s.store == nil {
		return activityservice.ResourceActivityPage{}, invalidArgument("kb service is not configured")
	}
	if s.activityStore == nil {
		return activityservice.ResourceActivityPage{}, invalidArgument("activity store is not configured")
	}
	if err := s.validateScope(workspaceID, subject); err != nil {
		return activityservice.ResourceActivityPage{}, err
	}
	visibleBases, err := s.listVisibleKnowledgeBasesForActivity(ctx, workspaceID, subject)
	if err != nil {
		return activityservice.ResourceActivityPage{}, err
	}
	sourceRefs, visibleAssets, err := s.visibleKnowledgeBaseActivitySources(ctx, workspaceID, visibleBases)
	if err != nil {
		return activityservice.ResourceActivityPage{}, err
	}
	activityservice.RefreshVisibleResourceActivity(ctx, s.activityProjector, s.repositoryStore, workspaceID, repos.ResourceTypeKBSource, visibleAssets, s.activityRefreshAfter, nil)
	visibleAssets = append(visibleAssets, visibleKnowledgeBaseAssets(visibleBases)...)

	listOpts := opts
	selectedAssetID := strings.TrimSpace(opts.AssetID)
	if selectedAssetID != "" {
		if _, ok := visibleKnowledgeBaseIDSet(visibleBases)[selectedAssetID]; ok {
			sourceIDs := sourceRefs.sourceIDsByKnowledgeBaseID[selectedAssetID]
			listOpts.AssetID = ""
			listOpts.AssetIDs = append([]string{selectedAssetID}, sourceIDs...)
		} else if sourceRefs.sourceByID[selectedAssetID] != nil {
			listOpts.AssetID = selectedAssetID
			listOpts.AssetIDs = nil
		} else {
			return activityservice.ResourceActivityPage{Items: []activityservice.ResourceActivityItem{}}, nil
		}
	} else {
		listOpts.AssetIDs = append(visibleKnowledgeBaseIDs(visibleBases), sourceRefs.allSourceIDs...)
	}
	page, err := activityservice.ListResourceActivity(ctx, s.activityStore, workspaceID, activityservice.ResourceActivityDescriptor{
		AssetType:     activitystore.AssetTypeKB,
		VisibleAssets: visibleAssets,
	}, listOpts)
	if err != nil {
		return activityservice.ResourceActivityPage{}, err
	}
	return decorateKnowledgeBaseActivityItems(page, visibleBases, sourceRefs), nil
}

func (s *KBService) listVisibleKnowledgeBasesForActivity(ctx context.Context, workspaceID, subject string) ([]kb.KnowledgeBase, error) {
	items := make([]kb.KnowledgeBase, 0)
	canAdministerWorkspace := s.canAdministerWorkspaceForRead(ctx, workspaceID, subject)
	for page := 1; ; page++ {
		result, err := s.store.ListKnowledgeBaseCollection(ctx, kbActivityVisibleCollectionOptions(workspaceID, subject, canAdministerWorkspace, page, kbActivityPageSize))
		if err != nil {
			return nil, err
		}
		if len(result.Items) > 0 {
			if err := s.decorateKnowledgeBaseAccessListWithWorkspaceAuthority(ctx, result.Items, subject, canAdministerWorkspace); err != nil {
				return nil, err
			}
			items = append(items, result.Items...)
		}
		if !result.HasMore || len(result.Items) == 0 {
			break
		}
	}
	return items, nil
}

type kbActivitySourceRefs struct {
	allSourceIDs                []string
	sourceByID                  map[string]*kb.Source
	knowledgeBaseByID           map[string]kb.KnowledgeBase
	sourceIDsByKnowledgeBaseID  map[string][]string
	knowledgeBaseIDBySourceID   map[string]string
	knowledgeBaseNameBySourceID map[string]string
}

func (s *KBService) visibleKnowledgeBaseActivitySources(ctx context.Context, workspaceID string, bases []kb.KnowledgeBase) (kbActivitySourceRefs, []activityservice.ResourceActivityVisibleAsset, error) {
	refs := kbActivitySourceRefs{
		sourceByID:                  map[string]*kb.Source{},
		knowledgeBaseByID:           map[string]kb.KnowledgeBase{},
		sourceIDsByKnowledgeBaseID:  map[string][]string{},
		knowledgeBaseIDBySourceID:   map[string]string{},
		knowledgeBaseNameBySourceID: map[string]string{},
	}
	kbIDs := make([]string, 0, len(bases))
	for _, base := range bases {
		id := strings.TrimSpace(base.ID)
		if id == "" {
			continue
		}
		kbIDs = append(kbIDs, id)
		refs.knowledgeBaseByID[id] = base
	}
	sources, err := s.store.ListActivitySourcesForKnowledgeBases(ctx, workspaceID, kbIDs)
	if err != nil {
		return refs, nil, err
	}
	visibleAssets := make([]activityservice.ResourceActivityVisibleAsset, 0, len(sources))
	for i := range sources {
		source := sources[i]
		sourceID := strings.TrimSpace(source.ID)
		kbID := strings.TrimSpace(source.KnowledgeBaseID)
		if sourceID == "" || kbID == "" {
			continue
		}
		base := refs.knowledgeBaseByID[kbID]
		refs.sourceByID[sourceID] = &sources[i]
		refs.sourceIDsByKnowledgeBaseID[kbID] = append(refs.sourceIDsByKnowledgeBaseID[kbID], sourceID)
		refs.knowledgeBaseIDBySourceID[sourceID] = kbID
		refs.knowledgeBaseNameBySourceID[sourceID] = base.Name
		refs.allSourceIDs = append(refs.allSourceIDs, sourceID)
		visibleAssets = append(visibleAssets, activityservice.ResourceActivityVisibleAsset{
			ID:   sourceID,
			Name: firstNonEmptyString(source.Name, base.Name),
		})
	}
	return refs, visibleAssets, nil
}

func visibleKnowledgeBaseAssets(bases []kb.KnowledgeBase) []activityservice.ResourceActivityVisibleAsset {
	visible := make([]activityservice.ResourceActivityVisibleAsset, 0, len(bases))
	for _, base := range bases {
		id := strings.TrimSpace(base.ID)
		if id == "" {
			continue
		}
		visible = append(visible, activityservice.ResourceActivityVisibleAsset{ID: id, Name: base.Name})
	}
	return visible
}

func visibleKnowledgeBaseIDs(bases []kb.KnowledgeBase) []string {
	ids := make([]string, 0, len(bases))
	for _, base := range bases {
		if id := strings.TrimSpace(base.ID); id != "" {
			ids = append(ids, id)
		}
	}
	return ids
}

func visibleKnowledgeBaseIDSet(bases []kb.KnowledgeBase) map[string]struct{} {
	out := make(map[string]struct{}, len(bases))
	for _, base := range bases {
		if id := strings.TrimSpace(base.ID); id != "" {
			out[id] = struct{}{}
		}
	}
	return out
}

func decorateKnowledgeBaseActivityItems(page activityservice.ResourceActivityPage, bases []kb.KnowledgeBase, refs kbActivitySourceRefs) activityservice.ResourceActivityPage {
	kbByID := refs.knowledgeBaseByID
	if kbByID == nil {
		kbByID = make(map[string]kb.KnowledgeBase, len(bases))
		for _, base := range bases {
			kbByID[base.ID] = base
		}
	}
	for i := range page.Items {
		item := &page.Items[i]
		if item.Details == nil {
			item.Details = map[string]any{}
		}
		assetID := strings.TrimSpace(item.AssetID)
		if source := refs.sourceByID[assetID]; source != nil {
			kbID := strings.TrimSpace(source.KnowledgeBaseID)
			base := kbByID[kbID]
			item.Details["knowledgeBaseId"] = kbID
			item.Details["knowledgeBaseID"] = kbID
			item.Details["knowledgeBaseName"] = base.Name
			item.Details["sourceId"] = source.ID
			item.Details["sourceID"] = source.ID
			item.Details["sourceName"] = source.Name
			item.Details["sourceType"] = source.SourceType
			if item.AssetName == "" {
				item.AssetName = firstNonEmptyString(source.Name, base.Name)
			}
			item.Display = activityservice.ResourceActivityDisplayForItem(*item)
			continue
		}
		if base, ok := kbByID[assetID]; ok {
			item.Details["knowledgeBaseId"] = base.ID
			item.Details["knowledgeBaseID"] = base.ID
			item.Details["knowledgeBaseName"] = base.Name
			if item.AssetName == "" {
				item.AssetName = base.Name
			}
			item.Display = activityservice.ResourceActivityDisplayForItem(*item)
			continue
		}
		if kbID := strings.TrimSpace(detailString(item.Details, "knowledgeBaseId", "knowledgeBaseID", "kbId", "kbID")); kbID != "" {
			if base, ok := kbByID[kbID]; ok {
				item.Details["knowledgeBaseName"] = base.Name
				item.Display = activityservice.ResourceActivityDisplayForItem(*item)
			}
		}
	}
	return page
}

func detailString(details map[string]any, keys ...string) string {
	for _, key := range keys {
		value := details[key]
		if text, ok := value.(string); ok && strings.TrimSpace(text) != "" {
			return strings.TrimSpace(text)
		}
	}
	return ""
}

func kbActivityVisibleCollectionOptions(workspaceID, subject string, canAdministerWorkspace bool, page, perPage int) kb.KnowledgeBaseCollectionOptions {
	return kb.KnowledgeBaseCollectionOptions{
		WorkspaceID:                 workspaceID,
		PrincipalID:                 subject,
		BypassPrincipalAccessFilter: canAdministerWorkspace,
		BypassDraftVisibilityFilter: canAdministerWorkspace,
		View:                        "all",
		IncludeInactive:             false,
		Page:                        page,
		PerPage:                     perPage,
	}
}

func (s *KBService) knowledgeBaseActivityStore(tx *kb.Store) *activitystore.Store {
	if s == nil || s.activityStore == nil {
		return nil
	}
	if tx != nil && tx.DB() != nil {
		return s.activityStore.WithDB(tx.DB())
	}
	return s.activityStore
}

func (s *KBService) writeKnowledgeBaseActivity(ctx context.Context, store *activitystore.Store, input activityservice.ResourceActivityEventInput) error {
	if s == nil || store == nil {
		return nil
	}
	return activityservice.WriteResourceActivityEvent(ctx, store, input)
}

func (s *KBService) writeKnowledgeBaseAuditActivity(ctx context.Context, event kb.AuditEvent) error {
	return s.writeKnowledgeBaseAuditActivityWithStore(ctx, s.knowledgeBaseActivityStore(nil), event)
}

func (s *KBService) writeKnowledgeBaseAuditActivityWithStore(ctx context.Context, store *activitystore.Store, event kb.AuditEvent) error {
	if s == nil || store == nil {
		return nil
	}
	details := kbActivityDetailsFromAudit(event)
	return s.writeKnowledgeBaseActivity(ctx, store, activityservice.ResourceActivityEventInput{
		WorkspaceID:   event.WorkspaceID,
		AssetType:     activitystore.AssetTypeKB,
		AssetID:       firstNonEmptyString(kbActivityAssetIDForAudit(event), event.KnowledgeBaseID),
		EventType:     kbActivityEventTypeForAudit(event),
		ActorID:       event.ActorID,
		SummaryKey:    kbActivityEventTypeForAudit(event),
		Details:       details,
		TargetKind:    kbActivityTargetKindForAudit(event),
		TargetID:      event.ResourceID,
		Source:        activitystore.SourceControl,
		SourceEventID: event.ID,
		OccurredAt:    event.CreatedAt,
	})
}

func (s *KBService) writeKnowledgeBaseJobActivity(ctx context.Context, job kb.Job) error {
	return s.writeKnowledgeBaseJobActivityWithStore(ctx, s.knowledgeBaseActivityStore(nil), job)
}

func (s *KBService) writeKnowledgeBaseJobActivityWithStore(ctx context.Context, store *activitystore.Store, job kb.Job) error {
	if s == nil || store == nil {
		return nil
	}
	activity, ok := overviewActivityFromJob(job)
	if !ok {
		return nil
	}
	actorType, actorID := s.knowledgeBaseJobActivityActor(ctx, job)
	details := map[string]any{
		"knowledgeBaseId": job.KnowledgeBaseID,
		"knowledgeBaseID": job.KnowledgeBaseID,
		"sourceId":        job.SourceID,
		"sourceID":        job.SourceID,
		"jobId":           job.ID,
		"jobType":         job.JobType,
		"status":          job.Status,
		"stage":           KBJobDisplayProgressForJob(job).Stage,
		"summary":         activity.Summary,
	}
	if strings.TrimSpace(job.Error) != "" {
		details["error"] = SanitizeKBProgressMessage(job.Error)
	}
	return s.writeKnowledgeBaseActivity(ctx, store, activityservice.ResourceActivityEventInput{
		WorkspaceID:   job.WorkspaceID,
		AssetType:     activitystore.AssetTypeKB,
		AssetID:       firstNonEmptyString(job.SourceID, job.KnowledgeBaseID),
		EventType:     kbActivityEventTypeForJob(job),
		ActorType:     actorType,
		ActorID:       actorID,
		SummaryKey:    kbActivityEventTypeForJob(job),
		Details:       details,
		TargetKind:    kbActivityTargetKindForJob(job),
		TargetID:      job.ID,
		Source:        activitystore.SourceControl,
		SourceEventID: job.ID + ":" + strings.TrimSpace(job.Status),
		OccurredAt:    firstTime(job.UpdatedAt, job.CreatedAt),
	})
}

func kbActivityTargetKindForJob(job kb.Job) string {
	if strings.TrimSpace(job.JobType) == KBJobTypeSyncSourceMirror {
		return "source_mirror_sync"
	}
	return "automation"
}

func (s *KBService) knowledgeBaseJobActivityActor(ctx context.Context, job kb.Job) (string, string) {
	if s == nil || s.store == nil {
		return activityservice.ActivityActorTypeSystem, ""
	}
	sourceID := strings.TrimSpace(job.SourceID)
	if sourceID == "" {
		return activityservice.ActivityActorTypeSystem, ""
	}
	source, err := s.store.GetSource(ctx, job.WorkspaceID, job.KnowledgeBaseID, sourceID)
	if err != nil {
		return activityservice.ActivityActorTypeSystem, ""
	}
	actor := firstNonEmptyString(source.UpdatedBy, source.CreatedBy)
	if strings.TrimSpace(job.JobType) == KBJobTypeSyncSourceMirror {
		if mirror, err := s.store.GetSourceMirror(ctx, job.WorkspaceID, job.KnowledgeBaseID, sourceID); err == nil {
			actor = firstNonEmptyString(mirror.UpdatedBy, mirror.CreatedBy, actor)
		}
	}
	if isSystemKBJobActor(actor) {
		return activityservice.ActivityActorTypeSystem, ""
	}
	return activityservice.ActivityActorTypeUser, actor
}

func isSystemKBJobActor(actor string) bool {
	switch strings.TrimSpace(actor) {
	case "", "meshx-kb-scheduler", "meshx-kb-mirror-worker":
		return true
	default:
		return false
	}
}

func kbActivityEventTypeForAudit(event kb.AuditEvent) string {
	switch strings.TrimSpace(event.Action) {
	case "kb.create":
		return "created"
	case "kb.update":
		return "updated"
	case "kb.source.create":
		return "source_created"
	case "kb.source.delete":
		return "source_deleted"
	case "kb.repository_access.grant":
		return "grant_added"
	case "kb.repository_access.update", "kb.repository_access.override.update", "kb.repository_access.override.grant", "kb.repository_access.override.delete":
		return "grant_role_changed"
	case "kb.repository_access.revoke":
		return "grant_removed"
	case "kb.repository_access.repair", "kb.repository_access.reconcile":
		return "settings_changed"
	case "kb.retrieval.search":
		return "retrieval_searched"
	case "kb.retrieval.read":
		return "retrieval_read"
	case "kb.retrieval.tree":
		return "retrieval_tree_listed"
	case "kb.source.code_wiki.generate":
		return "kb_source_mirror_sync_queued"
	default:
		return strings.TrimSpace(event.Action)
	}
}

func kbActivityEventTypeForJob(job kb.Job) string {
	activity, ok := overviewActivityFromJob(job)
	if !ok {
		return strings.TrimSpace(job.JobType)
	}
	return strings.ReplaceAll(strings.TrimSpace(activity.Action), ".", "_")
}

func kbActivityTargetKindForAudit(event kb.AuditEvent) string {
	switch strings.TrimSpace(event.ResourceType) {
	case "knowledge_base":
		return "resource"
	case "source":
		return "source"
	case "knowledge_search", "citation":
		return "retrieval"
	case "permission_grant", "repository_access", "source_repository_access_override":
		return "grant"
	default:
		return strings.TrimSpace(event.ResourceType)
	}
}

func kbActivityAssetIDForAudit(event kb.AuditEvent) string {
	if strings.TrimSpace(event.ResourceType) == "source" {
		return strings.TrimSpace(event.ResourceID)
	}
	return strings.TrimSpace(event.KnowledgeBaseID)
}

func kbActivityDetailsFromAudit(event kb.AuditEvent) map[string]any {
	details := map[string]any{}
	if strings.TrimSpace(event.Details) != "" {
		_ = json.Unmarshal([]byte(event.Details), &details)
		if details == nil {
			details = map[string]any{}
		}
	}
	details["knowledgeBaseId"] = event.KnowledgeBaseID
	details["knowledgeBaseID"] = event.KnowledgeBaseID
	details["action"] = event.Action
	details["resourceType"] = event.ResourceType
	details["resourceId"] = event.ResourceID
	if strings.TrimSpace(event.Summary) != "" {
		details["summary"] = event.Summary
	}
	if strings.TrimSpace(event.ResourceType) == "source" {
		details["sourceId"] = event.ResourceID
		details["sourceID"] = event.ResourceID
	}
	if sourceID := firstNonEmptyString(detailString(details, "source_id"), detailString(details, "sourceId"), detailString(details, "sourceID")); sourceID != "" {
		details["sourceId"] = sourceID
		details["sourceID"] = sourceID
	}
	return details
}
