// Package activity owns cross-resource activity service projection, display normalization, and authorized list contracts. Persisted events and checkpoints remain owned by internal/store/resource/activity; these service values are not AgentTeam message, task, channel, or timeline DTOs.
package activity

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

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

// ResourceActivityListOptions describes the authorized service-level activity query requested by a resource domain. It is not a persistence query model or API request DTO.
type ResourceActivityListOptions struct {
	AssetID   string
	AssetIDs  []string
	EventType string
	ActorID   string
	Cursor    string
	Limit     int
	Since     *time.Time
	Until     *time.Time
}

// ResourceActivityVisibleAsset is the already-authorized resource identity used
// to scope activity list visibility and attach current display names.
type ResourceActivityVisibleAsset struct {
	ID   string
	Name string
}

// ResourceActivityDescriptor captures the shared list-time activity visibility
// contract while leaving resource-specific event emission in each domain service.
type ResourceActivityDescriptor struct {
	AssetType           string
	VisibleAssets       []ResourceActivityVisibleAsset
	TombstoneActorID    string
	TombstoneEventTypes []string
}

// ListResourceActivity applies descriptor visibility, tombstone inclusion, and
// name hydration before converting store events into service activity items.
func ListResourceActivity(ctx context.Context, store *activitystore.Store, workspaceID string, descriptor ResourceActivityDescriptor, opts ResourceActivityListOptions) (ResourceActivityPage, error) {
	if store == nil {
		return ResourceActivityPage{}, ErrInvalidArgument
	}
	visibleIDs, assetNames := resourceActivityVisibleAssetIndex(descriptor.VisibleAssets)
	page, err := store.ListEvents(ctx, activitystore.ListOptions{
		WorkspaceID:         workspaceID,
		AssetType:           strings.TrimSpace(descriptor.AssetType),
		VisibleAssetIDs:     visibleIDs,
		TombstoneActorID:    strings.TrimSpace(descriptor.TombstoneActorID),
		TombstoneEventTypes: descriptor.TombstoneEventTypes,
		AssetID:             opts.AssetID,
		AssetIDs:            opts.AssetIDs,
		EventType:           opts.EventType,
		ActorID:             opts.ActorID,
		Cursor:              opts.Cursor,
		Limit:               opts.Limit,
		Since:               opts.Since,
		Until:               opts.Until,
	})
	if err != nil {
		return ResourceActivityPage{}, mapActivityStoreError(err)
	}
	return ResourceActivityPage{Items: resourceActivityItems(page.Items, assetNames), NextCursor: page.NextCursor}, nil
}

func resourceActivityVisibleAssetIndex(assets []ResourceActivityVisibleAsset) ([]string, map[string]string) {
	visibleIDs := make([]string, 0, len(assets))
	assetNames := make(map[string]string, len(assets))
	for _, asset := range assets {
		id := strings.TrimSpace(asset.ID)
		if id == "" {
			continue
		}
		visibleIDs = append(visibleIDs, id)
		assetNames[id] = asset.Name
	}
	return visibleIDs, assetNames
}

// ResourceActivityActor is the service projection of an activity actor after persistence decoding; it is not a AgentTeam user or transport DTO.
type ResourceActivityActor struct {
	Type string
	ID   string
	Name string
}

// ResourceActivityTarget is the service projection of the resource target referenced by an activity item, not a AgentTeam task or message target.
type ResourceActivityTarget struct {
	Kind string
	ID   string
	URL  string
}

// ResourceActivityItem is the resource-domain activity service value returned after authorization and display normalization; it is not a store model or API DTO.
type ResourceActivityItem struct {
	ID            string
	WorkspaceID   string
	AssetType     string
	AssetID       string
	AssetName     string
	RepoFullName  string
	EventType     string
	Actor         ResourceActivityActor
	SummaryKey    string
	Details       map[string]any
	Display       ResourceActivityDisplay
	Target        ResourceActivityTarget
	Source        string
	SourceEventID string
	OccurredAt    time.Time
}

// ResourceActivityPage is the service pagination result for authorized resource activity and carries the opaque next cursor without owning its wire encoding.
type ResourceActivityPage struct {
	Items      []ResourceActivityItem
	NextCursor string
}

func resourceActivityItems(events []activitystore.ResourceActivityEvent, assetNames map[string]string) []ResourceActivityItem {
	items := make([]ResourceActivityItem, 0, len(events))
	for _, event := range events {
		items = append(items, resourceActivityItem(event, assetNames[event.AssetID]))
	}
	return items
}

func resourceActivityItem(event activitystore.ResourceActivityEvent, assetName string) ResourceActivityItem {
	details := parseActivityDetails(event.DetailsJSON)
	if strings.TrimSpace(assetName) == "" {
		if name, ok := details["name"].(string); ok {
			assetName = strings.TrimSpace(name)
		}
	}
	display, _ := displayFromDetails(details)
	display = completedResourceActivityDisplay(display, event, details, assetName)
	if strings.TrimSpace(assetName) == "" {
		assetName = display.Resource.Name
	}
	return ResourceActivityItem{
		ID:           event.ID,
		WorkspaceID:  event.WorkspaceID,
		AssetType:    event.AssetType,
		AssetID:      event.AssetID,
		AssetName:    assetName,
		RepoFullName: event.RepoFullName,
		EventType:    event.EventType,
		Actor: ResourceActivityActor{
			Type: event.ActorType,
			ID:   event.ActorID,
			Name: event.ActorName,
		},
		SummaryKey: event.SummaryKey,
		Details:    details,
		Display:    display,
		Target: ResourceActivityTarget{
			Kind: event.TargetKind,
			ID:   event.TargetID,
			URL:  event.TargetURL,
		},
		Source:        event.Source,
		SourceEventID: event.SourceEventIDValue(),
		OccurredAt:    event.OccurredAt,
	}
}

func parseActivityDetails(raw string) map[string]any {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return map[string]any{}
	}
	var details map[string]any
	if err := json.Unmarshal([]byte(raw), &details); err != nil || details == nil {
		return map[string]any{"raw": raw}
	}
	return details
}

func mapActivityStoreError(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, activitystore.ErrInvalidCursor) {
		return ErrInvalidCursor
	}
	return err
}
