// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/tag/update.go
package tag

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"

	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
	dom_tag "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/tag"
)

type UpdateTagUseCase struct {
	tagRepo        dom_tag.Repository
	collectionRepo dom_collection.CollectionRepository
	fileRepo       dom_file.FileMetadataRepository
	logger         *zap.Logger
}

func NewUpdateTagUseCase(
	tagRepo dom_tag.Repository,
	collectionRepo dom_collection.CollectionRepository,
	fileRepo dom_file.FileMetadataRepository,
	logger *zap.Logger,
) *UpdateTagUseCase {
	return &UpdateTagUseCase{
		tagRepo:        tagRepo,
		collectionRepo: collectionRepo,
		fileRepo:       fileRepo,
		logger:         logger.Named("UpdateTagUseCase"),
	}
}

// Execute updates a tag with new encrypted data (E2EE)
// The client must provide the complete updated tag with newly encrypted fields
// This will propagate the tag update to all collections and files that have this tag embedded
func (uc *UpdateTagUseCase) Execute(ctx context.Context, tag *dom_tag.Tag) error {
	// Validate encrypted data is present
	if tag.EncryptedName == "" {
		return fmt.Errorf("encrypted tag name is required")
	}
	if tag.EncryptedColor == "" {
		return fmt.Errorf("encrypted tag color is required")
	}
	if tag.EncryptedTagKey == nil || len(tag.EncryptedTagKey.Ciphertext) == 0 {
		return fmt.Errorf("encrypted tag key is required")
	}

	// Update modified timestamp
	tag.ModifiedAt = time.Now()

	// Backend never sees plaintext - only validates encrypted data exists and updates
	if err := uc.tagRepo.Update(ctx, tag); err != nil {
		return fmt.Errorf("failed to update tag: %w", err)
	}

	// Propagate tag updates to all collections and files that have this tag embedded
	// This runs asynchronously in the background to avoid blocking the API response
	go uc.propagateTagUpdate(context.Background(), tag)

	return nil
}

// propagateTagUpdate updates all collections and files that have this tag embedded
// This is called asynchronously after the tag is updated
func (uc *UpdateTagUseCase) propagateTagUpdate(ctx context.Context, tag *dom_tag.Tag) {
	uc.logger.Info("🏷️  TAG PROPAGATION: Starting tag update propagation",
		zap.String("tag_id", tag.ID.String()))

	// Create the updated embedded tag
	updatedEmbeddedTag := tag.ToEmbeddedTag()

	// Update all collections with this tag
	collections, err := uc.collectionRepo.ListByTagID(ctx, tag.ID)
	if err != nil {
		uc.logger.Error("🏷️  TAG PROPAGATION: Failed to list collections by tag",
			zap.String("tag_id", tag.ID.String()),
			zap.Error(err))
	} else {
		uc.logger.Info("🏷️  TAG PROPAGATION: Found collections to update",
			zap.String("tag_id", tag.ID.String()),
			zap.Int("count", len(collections)))

		for _, collection := range collections {
			// Update the embedded tag in the collection
			updated := false
			for i, embeddedTag := range collection.Tags {
				if embeddedTag.ID == tag.ID {
					collection.Tags[i] = *updatedEmbeddedTag
					updated = true
					break
				}
			}

			if updated {
				collection.ModifiedAt = time.Now()
				if err := uc.collectionRepo.Update(ctx, collection); err != nil {
					uc.logger.Error("🏷️  TAG PROPAGATION: Failed to update collection",
						zap.String("tag_id", tag.ID.String()),
						zap.String("collection_id", collection.ID.String()),
						zap.Error(err))
				} else {
					uc.logger.Debug("🏷️  TAG PROPAGATION: Updated collection",
						zap.String("tag_id", tag.ID.String()),
						zap.String("collection_id", collection.ID.String()))
				}
			}
		}
	}

	// Update all files with this tag
	files, err := uc.fileRepo.ListByTagID(ctx, tag.ID)
	if err != nil {
		uc.logger.Error("🏷️  TAG PROPAGATION: Failed to list files by tag",
			zap.String("tag_id", tag.ID.String()),
			zap.Error(err))
	} else {
		uc.logger.Info("🏷️  TAG PROPAGATION: Found files to update",
			zap.String("tag_id", tag.ID.String()),
			zap.Int("count", len(files)))

		for _, file := range files {
			// Update the embedded tag in the file
			updated := false
			for i, embeddedTag := range file.Tags {
				if embeddedTag.ID == tag.ID {
					file.Tags[i] = *updatedEmbeddedTag
					updated = true
					break
				}
			}

			if updated {
				file.ModifiedAt = time.Now()
				if err := uc.fileRepo.Update(file); err != nil {
					uc.logger.Error("🏷️  TAG PROPAGATION: Failed to update file",
						zap.String("tag_id", tag.ID.String()),
						zap.String("file_id", file.ID.String()),
						zap.Error(err))
				} else {
					uc.logger.Debug("🏷️  TAG PROPAGATION: Updated file",
						zap.String("tag_id", tag.ID.String()),
						zap.String("file_id", file.ID.String()))
				}
			}
		}
	}

	uc.logger.Info("🏷️  TAG PROPAGATION: Completed tag update propagation",
		zap.String("tag_id", tag.ID.String()))
}
