package app

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/client"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/e2ee"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/inputvalidation"
)

// ============================================================================
// Tag Management
// ============================================================================

// TagData represents a decrypted tag for the frontend
type TagData struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	Color      string `json:"color"`
	CreatedAt  string `json:"created_at"`
	ModifiedAt string `json:"modified_at"`
	Version    uint64 `json:"version"`
	State      string `json:"state"`
}

// ListTags fetches all tags for the current user and decrypts them
func (a *Application) ListTags() ([]*TagData, error) {
	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Fetch tags from API
	tags, err := apiClient.ListTags(a.ctx)
	if err != nil {
		a.logger.Error("Failed to fetch tags from API", zap.Error(err))
		return nil, fmt.Errorf("failed to fetch tags: %w", err)
	}

	if len(tags) == 0 {
		return []*TagData{}, nil
	}

	// Get master key for decryption
	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		a.logger.Error("Failed to get master key", zap.Error(err))
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	// Decrypt tags
	decryptedTags := make([]*TagData, 0, len(tags))
	for _, tag := range tags {
		decrypted, err := a.decryptTag(tag, masterKey)
		if err != nil {
			a.logger.Error("Failed to decrypt tag",
				zap.String("tag_id", tag.ID),
				zap.Error(err))
			continue // Skip tags that fail to decrypt
		}
		decryptedTags = append(decryptedTags, decrypted)
	}

	a.logger.Info("Tags fetched and decrypted successfully",
		zap.Int("total", len(tags)),
		zap.Int("decrypted", len(decryptedTags)))

	return decryptedTags, nil
}

// decryptTag decrypts a single tag using the master key
func (a *Application) decryptTag(tag *client.Tag, masterKey []byte) (*TagData, error) {
	// Decode encrypted tag key
	if tag.EncryptedTagKey == nil {
		return nil, fmt.Errorf("tag has no encrypted tag key")
	}

	// Decode base64 nonce and ciphertext
	keyNonce, err := base64.StdEncoding.DecodeString(tag.EncryptedTagKey.Nonce)
	if err != nil {
		// Try URL-safe encoding without padding
		keyNonce, err = base64.RawURLEncoding.DecodeString(tag.EncryptedTagKey.Nonce)
		if err != nil {
			return nil, fmt.Errorf("failed to decode tag key nonce: %w", err)
		}
	}

	keyCiphertext, err := base64.StdEncoding.DecodeString(tag.EncryptedTagKey.Ciphertext)
	if err != nil {
		// Try URL-safe encoding without padding
		keyCiphertext, err = base64.RawURLEncoding.DecodeString(tag.EncryptedTagKey.Ciphertext)
		if err != nil {
			return nil, fmt.Errorf("failed to decode tag key ciphertext: %w", err)
		}
	}

	// Extract actual ciphertext (skip nonce if it's prepended)
	var actualCiphertext []byte
	if len(keyCiphertext) > len(keyNonce) && bytes.Equal(keyCiphertext[:len(keyNonce)], keyNonce) {
		// Nonce is prepended to ciphertext
		actualCiphertext = keyCiphertext[len(keyNonce):]
	} else {
		actualCiphertext = keyCiphertext
	}

	// Decrypt tag key using XSalsa20-Poly1305 (SecretBox)
	tagKey, err := e2ee.DecryptTagKey(&e2ee.EncryptedKey{
		Ciphertext: actualCiphertext,
		Nonce:      keyNonce,
	}, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt tag key: %w", err)
	}

	// Decrypt tag name
	name, err := decryptTagField(tag.EncryptedName, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt tag name: %w", err)
	}

	// Decrypt tag color
	color, err := decryptTagField(tag.EncryptedColor, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt tag color: %w", err)
	}

	return &TagData{
		ID:         tag.ID,
		Name:       name,
		Color:      color,
		CreatedAt:  tag.CreatedAt.Format(time.RFC3339),
		ModifiedAt: tag.ModifiedAt.Format(time.RFC3339),
		Version:    tag.Version,
		State:      tag.State,
	}, nil
}

// decryptTagField decrypts an encrypted tag field (name or color)
// Format: "ciphertext:nonce" both in base64
func decryptTagField(encryptedField string, tagKey []byte) (string, error) {
	// Split by colon to get ciphertext and nonce
	parts := bytes.Split([]byte(encryptedField), []byte(":"))
	if len(parts) != 2 {
		return "", fmt.Errorf("invalid encrypted field format (expected 'ciphertext:nonce')")
	}

	ciphertextB64 := string(parts[0])
	nonceB64 := string(parts[1])

	// Decode base64 (try URL-safe without padding first, then standard)
	ciphertext, err := base64.RawURLEncoding.DecodeString(ciphertextB64)
	if err != nil {
		ciphertext, err = base64.StdEncoding.DecodeString(ciphertextB64)
		if err != nil {
			return "", fmt.Errorf("failed to decode ciphertext: %w", err)
		}
	}

	nonce, err := base64.RawURLEncoding.DecodeString(nonceB64)
	if err != nil {
		nonce, err = base64.StdEncoding.DecodeString(nonceB64)
		if err != nil {
			return "", fmt.Errorf("failed to decode nonce: %w", err)
		}
	}

	// Decrypt using XSalsa20-Poly1305
	decrypted, err := e2ee.DecryptWithSecretBox(ciphertext, nonce, tagKey)
	if err != nil {
		return "", fmt.Errorf("failed to decrypt field: %w", err)
	}

	return string(decrypted), nil
}

// CreateTag creates a new tag with encrypted name and color
func (a *Application) CreateTag(name, color string) (*TagData, error) {
	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Get master key for encryption
	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		a.logger.Error("Failed to get master key", zap.Error(err))
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	// Generate new tag key (32 bytes for XSalsa20-Poly1305)
	tagKey := e2ee.GenerateKey()

	// Encrypt tag name
	encryptedName, err := encryptTagField(name, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag name: %w", err)
	}

	// Encrypt tag color
	encryptedColor, err := encryptTagField(color, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag color: %w", err)
	}

	// Encrypt tag key with master key
	encryptedTagKey, err := e2ee.EncryptTagKeySecretBox(tagKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag key: %w", err)
	}

	// Prepare API request
	tagID := uuid.New().String()
	now := time.Now()

	input := &client.CreateTagInput{
		ID:             tagID,
		EncryptedName:  encryptedName,
		EncryptedColor: encryptedColor,
		EncryptedTagKey: &client.EncryptedTagKey{
			Ciphertext: base64.StdEncoding.EncodeToString(append(encryptedTagKey.Nonce, encryptedTagKey.Ciphertext...)),
			Nonce:      base64.StdEncoding.EncodeToString(encryptedTagKey.Nonce),
			KeyVersion: 1,
		},
		CreatedAt:  now.Format(time.RFC3339),
		ModifiedAt: now.Format(time.RFC3339),
		Version:    1,
		State:      "active",
	}

	// Create tag via API
	tag, err := apiClient.CreateTag(a.ctx, input)
	if err != nil {
		a.logger.Error("Failed to create tag", zap.Error(err))
		return nil, fmt.Errorf("failed to create tag: %w", err)
	}

	a.logger.Info("Tag created successfully", zap.String("tag_id", tag.ID))

	return &TagData{
		ID:         tag.ID,
		Name:       name,
		Color:      color,
		CreatedAt:  tag.CreatedAt.Format(time.RFC3339),
		ModifiedAt: tag.ModifiedAt.Format(time.RFC3339),
		Version:    tag.Version,
		State:      tag.State,
	}, nil
}

// encryptTagField encrypts a tag field (name or color) with the tag key
// Returns format: "ciphertext:nonce" both in base64
func encryptTagField(plaintext string, tagKey []byte) (string, error) {
	encrypted, err := e2ee.EncryptWithSecretBox([]byte(plaintext), tagKey)
	if err != nil {
		return "", err
	}

	// Encode to base64 (URL-safe without padding to match web app)
	ciphertextB64 := base64.RawURLEncoding.EncodeToString(encrypted.Ciphertext)
	nonceB64 := base64.RawURLEncoding.EncodeToString(encrypted.Nonce)

	return fmt.Sprintf("%s:%s", ciphertextB64, nonceB64), nil
}

// UpdateTag updates an existing tag's name and/or color
func (a *Application) UpdateTag(tagID, name, color string) (*TagData, error) {
	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Get current tag to retrieve encrypted tag key
	currentTag, err := apiClient.GetTag(a.ctx, tagID)
	if err != nil {
		a.logger.Error("Failed to get current tag", zap.String("tag_id", tagID), zap.Error(err))
		return nil, fmt.Errorf("failed to get current tag: %w", err)
	}

	// Get master key for encryption
	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		a.logger.Error("Failed to get master key", zap.Error(err))
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	// Decrypt tag key
	keyNonce, err := base64.StdEncoding.DecodeString(currentTag.EncryptedTagKey.Nonce)
	if err != nil {
		keyNonce, err = base64.RawURLEncoding.DecodeString(currentTag.EncryptedTagKey.Nonce)
		if err != nil {
			return nil, fmt.Errorf("failed to decode tag key nonce: %w", err)
		}
	}

	keyCiphertext, err := base64.StdEncoding.DecodeString(currentTag.EncryptedTagKey.Ciphertext)
	if err != nil {
		keyCiphertext, err = base64.RawURLEncoding.DecodeString(currentTag.EncryptedTagKey.Ciphertext)
		if err != nil {
			return nil, fmt.Errorf("failed to decode tag key ciphertext: %w", err)
		}
	}

	// Extract actual ciphertext
	var actualCiphertext []byte
	if len(keyCiphertext) > len(keyNonce) && bytes.Equal(keyCiphertext[:len(keyNonce)], keyNonce) {
		actualCiphertext = keyCiphertext[len(keyNonce):]
	} else {
		actualCiphertext = keyCiphertext
	}

	tagKey, err := e2ee.DecryptTagKey(&e2ee.EncryptedKey{
		Ciphertext: actualCiphertext,
		Nonce:      keyNonce,
	}, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt tag key: %w", err)
	}

	// Encrypt new name and color
	encryptedName, err := encryptTagField(name, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag name: %w", err)
	}

	encryptedColor, err := encryptTagField(color, tagKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag color: %w", err)
	}

	// Prepare update request (must include encrypted_tag_key and other required fields)
	input := &client.UpdateTagInput{
		EncryptedName:   encryptedName,
		EncryptedColor:  encryptedColor,
		EncryptedTagKey: currentTag.EncryptedTagKey, // Keep existing key
		CreatedAt:       currentTag.CreatedAt.Format(time.RFC3339),
		ModifiedAt:      time.Now().Format(time.RFC3339),
		Version:         currentTag.Version,
		State:           currentTag.State,
	}

	// Update tag via API
	tag, err := apiClient.UpdateTag(a.ctx, tagID, input)
	if err != nil {
		a.logger.Error("Failed to update tag", zap.String("tag_id", tagID), zap.Error(err))
		return nil, fmt.Errorf("failed to update tag: %w", err)
	}

	a.logger.Info("Tag updated successfully", zap.String("tag_id", tag.ID))

	return &TagData{
		ID:         tag.ID,
		Name:       name,
		Color:      color,
		CreatedAt:  tag.CreatedAt.Format(time.RFC3339),
		ModifiedAt: tag.ModifiedAt.Format(time.RFC3339),
		Version:    tag.Version,
		State:      tag.State,
	}, nil
}

// DeleteTag deletes a tag
func (a *Application) DeleteTag(tagID string) error {
	a.logger.Info("DeleteTag called", zap.String("tag_id", tagID))

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		a.logger.Error("API client not available")
		return fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		a.logger.Error("No active session", zap.Error(err))
		return fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		a.logger.Error("Session expired")
		return fmt.Errorf("session expired - please log in again")
	}

	a.logger.Info("Session valid, setting tokens", zap.String("tag_id", tagID))

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	a.logger.Info("Calling API DeleteTag", zap.String("tag_id", tagID))

	// Delete tag via API
	err = apiClient.DeleteTag(a.ctx, tagID)
	if err != nil {
		a.logger.Error("Failed to delete tag", zap.String("tag_id", tagID), zap.Error(err))
		return fmt.Errorf("failed to delete tag: %w", err)
	}

	a.logger.Info("Tag deleted successfully", zap.String("tag_id", tagID))
	return nil
}

// ============================================================================
// Tag Assignment Operations
// ============================================================================

// AssignTagToFile assigns a tag to a file
func (a *Application) AssignTagToFile(tagID, fileID string) error {
	// Validate inputs
	if err := inputvalidation.ValidateTagID(tagID); err != nil {
		return err
	}
	if err := inputvalidation.ValidateFileID(fileID); err != nil {
		return err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Call backend API
	input := &client.CreateTagAssignmentInput{
		TagID:      tagID,
		EntityID:   fileID,
		EntityType: "file",
	}

	_, err = apiClient.AssignTag(a.ctx, input)
	if err != nil {
		a.logger.Error("Failed to assign tag to file",
			zap.String("tag_id", tagID),
			zap.String("file_id", fileID),
			zap.Error(err))
		return fmt.Errorf("failed to assign tag: %w", err)
	}

	a.logger.Info("Tag assigned to file",
		zap.String("tag_id", tagID),
		zap.String("file_id", fileID))
	return nil
}

// UnassignTagFromFile removes a tag from a file
func (a *Application) UnassignTagFromFile(tagID, fileID string) error {
	// Validate inputs
	if err := inputvalidation.ValidateTagID(tagID); err != nil {
		return err
	}
	if err := inputvalidation.ValidateFileID(fileID); err != nil {
		return err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Call backend API
	err = apiClient.UnassignTag(a.ctx, tagID, fileID, "file")
	if err != nil {
		a.logger.Error("Failed to unassign tag from file",
			zap.String("tag_id", tagID),
			zap.String("file_id", fileID),
			zap.Error(err))
		return fmt.Errorf("failed to unassign tag: %w", err)
	}

	a.logger.Info("Tag unassigned from file",
		zap.String("tag_id", tagID),
		zap.String("file_id", fileID))
	return nil
}

// GetTagsForFile returns all tags assigned to a file (decrypted)
func (a *Application) GetTagsForFile(fileID string) ([]*TagData, error) {
	// Validate input
	if err := inputvalidation.ValidateFileID(fileID); err != nil {
		return nil, err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Get master key for decryption
	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		a.logger.Error("Failed to get master key", zap.Error(err))
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	// Fetch tags for entity from API
	tags, err := apiClient.GetTagsForEntity(a.ctx, fileID, "file")
	if err != nil {
		a.logger.Error("Failed to get tags for file",
			zap.String("file_id", fileID),
			zap.Error(err))
		return nil, fmt.Errorf("failed to get tags: %w", err)
	}

	if len(tags) == 0 {
		return []*TagData{}, nil
	}

	// Decrypt each tag
	result := make([]*TagData, 0, len(tags))
	for _, tag := range tags {
		decryptedTag, err := a.decryptTag(tag, masterKey)
		if err != nil {
			a.logger.Warn("Failed to decrypt tag, skipping",
				zap.String("tag_id", tag.ID),
				zap.Error(err))
			continue
		}
		result = append(result, decryptedTag)
	}

	a.logger.Info("Tags fetched for file",
		zap.String("file_id", fileID),
		zap.Int("count", len(result)))

	return result, nil
}

// ============================================================================
// Collection Tag Assignment Operations
// ============================================================================

// AssignTagToCollection assigns a tag to a collection
func (a *Application) AssignTagToCollection(tagID, collectionID string) error {
	// Validate inputs
	if err := inputvalidation.ValidateTagID(tagID); err != nil {
		return err
	}
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Call backend API
	input := &client.CreateTagAssignmentInput{
		TagID:      tagID,
		EntityID:   collectionID,
		EntityType: "collection",
	}

	_, err = apiClient.AssignTag(a.ctx, input)
	if err != nil {
		a.logger.Error("Failed to assign tag to collection",
			zap.String("tag_id", tagID),
			zap.String("collection_id", collectionID),
			zap.Error(err))
		return fmt.Errorf("failed to assign tag: %w", err)
	}

	a.logger.Info("Tag assigned to collection",
		zap.String("tag_id", tagID),
		zap.String("collection_id", collectionID))
	return nil
}

// UnassignTagFromCollection removes a tag from a collection
func (a *Application) UnassignTagFromCollection(tagID, collectionID string) error {
	// Validate inputs
	if err := inputvalidation.ValidateTagID(tagID); err != nil {
		return err
	}
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Call backend API
	err = apiClient.UnassignTag(a.ctx, tagID, collectionID, "collection")
	if err != nil {
		a.logger.Error("Failed to unassign tag from collection",
			zap.String("tag_id", tagID),
			zap.String("collection_id", collectionID),
			zap.Error(err))
		return fmt.Errorf("failed to unassign tag: %w", err)
	}

	a.logger.Info("Tag unassigned from collection",
		zap.String("tag_id", tagID),
		zap.String("collection_id", collectionID))
	return nil
}

// GetTagsForCollection returns all tags assigned to a collection (decrypted)
func (a *Application) GetTagsForCollection(collectionID string) ([]*TagData, error) {
	// Validate input
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return nil, err
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Get master key for decryption
	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		a.logger.Error("Failed to get master key", zap.Error(err))
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	// Fetch tags for entity from API
	tags, err := apiClient.GetTagsForEntity(a.ctx, collectionID, "collection")
	if err != nil {
		a.logger.Error("Failed to get tags for collection",
			zap.String("collection_id", collectionID),
			zap.Error(err))
		return nil, fmt.Errorf("failed to get tags: %w", err)
	}

	if len(tags) == 0 {
		return []*TagData{}, nil
	}

	// Decrypt each tag
	result := make([]*TagData, 0, len(tags))
	for _, tag := range tags {
		decryptedTag, err := a.decryptTag(tag, masterKey)
		if err != nil {
			a.logger.Warn("Failed to decrypt tag, skipping",
				zap.String("tag_id", tag.ID),
				zap.Error(err))
			continue
		}
		result = append(result, decryptedTag)
	}

	a.logger.Info("Tags fetched for collection",
		zap.String("collection_id", collectionID),
		zap.Int("count", len(result)))

	return result, nil
}

// ============================================================================
// Tag Search Operations
// ============================================================================

// SearchByTagsResult represents the result of a multi-tag search
type SearchByTagsResult struct {
	CollectionIDs   []string `json:"collection_ids"`
	FileIDs         []string `json:"file_ids"`
	TagCount        int      `json:"tag_count"`
	CollectionCount int      `json:"collection_count"`
	FileCount       int      `json:"file_count"`
}

// SearchByTags searches for collections and files that have ALL the specified tags
func (a *Application) SearchByTags(tagIDs []string, limit int) (*SearchByTagsResult, error) {
	a.logger.Info("SearchByTags called",
		zap.Int("tag_count", len(tagIDs)),
		zap.Int("limit", limit))

	// Validate inputs
	if len(tagIDs) == 0 {
		return nil, fmt.Errorf("at least one tag ID is required")
	}

	// Get API client from auth service
	apiClient := a.authService.GetAPIClient()
	if apiClient == nil {
		return nil, fmt.Errorf("API client not available")
	}

	// Ensure we have a valid session with tokens
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return nil, fmt.Errorf("no active session - please log in")
	}

	if !session.IsValid() {
		return nil, fmt.Errorf("session expired - please log in again")
	}

	// Ensure tokens are set in the API client
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Call backend API
	resp, err := apiClient.SearchByTags(a.ctx, tagIDs, limit)
	if err != nil {
		a.logger.Error("Failed to search by tags", zap.Error(err))
		return nil, fmt.Errorf("failed to search by tags: %w", err)
	}

	// Extract IDs only - frontend will fetch full details as needed
	collectionIDs := make([]string, 0, len(resp.Collections))
	for _, coll := range resp.Collections {
		collectionIDs = append(collectionIDs, coll.ID)
	}

	fileIDs := make([]string, 0, len(resp.Files))
	for _, file := range resp.Files {
		fileIDs = append(fileIDs, file.ID)
	}

	result := &SearchByTagsResult{
		CollectionIDs:   collectionIDs,
		FileIDs:         fileIDs,
		TagCount:        resp.TagCount,
		CollectionCount: len(collectionIDs),
		FileCount:       len(fileIDs),
	}

	a.logger.Info("SearchByTags completed",
		zap.Int("collections", len(collectionIDs)),
		zap.Int("files", len(fileIDs)))

	return result, nil
}
