package app

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"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"
)

type CollectionData struct {
	ID             string             `json:"id"`
	Name           string             `json:"name"`
	Description    string             `json:"description,omitempty"`
	CustomIcon     string             `json:"custom_icon,omitempty"` // Decrypted custom icon (emoji or "icon:<id>")
	ParentID       string             `json:"parent_id,omitempty"`
	CollectionType string             `json:"collection_type"` // "folder" or "album"
	IsShared       bool               `json:"is_shared"`
	TotalFiles     int                `json:"file_count"` // Maps to backend's "file_count" JSON field
	CreatedAt      string             `json:"created_at"`
	ModifiedAt     string             `json:"modified_at"`
	Tags           []*EmbeddedTagData `json:"tags"` // Tags assigned to this collection
}

// FileData represents a file for the frontend
type FileData struct {
	ID           string `json:"id"`
	CollectionID string `json:"collection_id"`
	Filename     string `json:"filename"`
	Size         int64  `json:"size"`
	ContentType  string `json:"content_type,omitempty"`
	CreatedAt    string `json:"created_at"`
	ModifiedAt   string `json:"modified_at"`
	// Sync status: "cloud_only", "local_only", "synced", "modified_locally"
	SyncStatus string `json:"sync_status"`
	// HasLocalContent indicates if the file content is available locally
	HasLocalContent bool `json:"has_local_content"`
	// LocalFilePath is the path to the decrypted file on disk (if available)
	LocalFilePath string `json:"local_file_path,omitempty"`
	// Tags assigned to this file
	Tags []*EmbeddedTagData `json:"tags"`
}

// extractActualCiphertext handles the difference between web frontend and legacy native app
// encryption formats for collection keys:
//   - Web frontend format: ciphertext contains nonce + encrypted_data (combined)
//   - Legacy native format: ciphertext contains just encrypted_data
//
// This function detects the format by checking if the ciphertext starts with the nonce,
// and extracts just the encrypted data if it's in combined format.
func extractActualCiphertext(ciphertext, nonce []byte) []byte {
	// Only check for combined format if we have XSalsa20 (24-byte nonce)
	// and the ciphertext is long enough to contain both nonce and data
	if len(ciphertext) > len(nonce) && len(nonce) == 24 {
		// Check if the ciphertext starts with the nonce (combined format)
		if bytes.Equal(ciphertext[:len(nonce)], nonce) {
			// Combined format: extract just the encrypted data
			return ciphertext[len(nonce):]
		}
	}
	// Legacy format or not combined, use as-is
	return ciphertext
}

// ListCollections returns all collections for the current user
func (a *Application) ListCollections() ([]*CollectionData, error) {
	// Get current session
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		a.logger.Error("Failed to get current session", zap.Error(err))
		return nil, fmt.Errorf("not authenticated: %w", err)
	}

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

	// Get the cached master key
	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()

	apiClient := a.authService.GetAPIClient()

	// Ensure tokens are set in the API client
	// This is important after app restarts or hot reloads
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Define a custom response struct that matches the actual backend API
	// (the client SDK's Collection struct is incorrect)
	type collectionAPIItem struct {
		ID                     string `json:"id"`
		EncryptedName          string `json:"encrypted_name"`
		EncryptedCustomIcon    string `json:"encrypted_custom_icon,omitempty"`
		EncryptedCollectionKey struct {
			Ciphertext string `json:"ciphertext"`
			Nonce      string `json:"nonce"`
		} `json:"encrypted_collection_key"`
		CollectionType string `json:"collection_type"`
		ParentID       string `json:"parent_id,omitempty"`
		UserID         string `json:"user_id"`
		TotalFiles     int    `json:"file_count"`
		CreatedAt      string `json:"created_at"`
		ModifiedAt     string `json:"modified_at"`
		SharedWith     []struct {
			RecipientEmail string `json:"recipient_email"`
		} `json:"shared_with,omitempty"`
		Tags []struct {
			ID             string `json:"id"`
			EncryptedName  string `json:"encrypted_name"`
			EncryptedColor string `json:"encrypted_color"`
			EncryptedTagKey struct {
				Ciphertext string `json:"ciphertext"`
				Nonce      string `json:"nonce"`
			} `json:"encrypted_tag_key"`
		} `json:"tags,omitempty"`
	}

	type listCollectionsResponse struct {
		Collections []*collectionAPIItem `json:"collections"`
	}

	// Make the HTTP request - use a helper function to handle 401 retry with token refresh
	var apiResponse listCollectionsResponse
	makeRequest := func() (*http.Response, error) {
		req, err := http.NewRequestWithContext(a.ctx, "GET", apiClient.GetBaseURL()+"/api/v1/collections", nil)
		if err != nil {
			return nil, err
		}
		// Get the latest access token from the API client (may have been refreshed)
		accessToken, _ := apiClient.GetTokens()
		req.Header.Set("Authorization", "Bearer "+accessToken)
		return a.httpClient.Do(req)
	}

	resp, err := makeRequest()
	if err != nil {
		a.logger.Error("Failed to send list collections request", zap.Error(err))
		return nil, fmt.Errorf("failed to send request: %w", err)
	}

	// Handle 401 by trying to refresh the token
	if resp.StatusCode == http.StatusUnauthorized {
		resp.Body.Close()
		a.logger.Info("Got 401, attempting token refresh before retry")

		// Try to refresh the token using the SDK's refresh mechanism
		if err := apiClient.RefreshToken(a.ctx); err != nil {
			a.logger.Error("Token refresh failed", zap.Error(err))
			return nil, fmt.Errorf("authentication failed - please log in again: %w", err)
		}

		// Retry the request with the new token
		resp, err = makeRequest()
		if err != nil {
			a.logger.Error("Failed to retry list collections request after token refresh", zap.Error(err))
			return nil, fmt.Errorf("failed to send request: %w", err)
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		a.logger.Error("Failed to list collections",
			zap.Int("status", resp.StatusCode),
			zap.String("body", string(body)))
		return nil, fmt.Errorf("failed to list collections: %s", string(body))
	}

	if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
		a.logger.Error("Failed to decode list collections response", zap.Error(err))
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	// Log detailed API response info for debugging
	a.logger.Info("API response received",
		zap.Int("collections_count", len(apiResponse.Collections)))
	if len(apiResponse.Collections) == 0 {
		a.logger.Warn("No collections returned from API - this may indicate an issue with the backend query or user has no collections")
	}
	for i, coll := range apiResponse.Collections {
		a.logger.Debug("Raw collection from API",
			zap.Int("index", i),
			zap.String("id", coll.ID),
			zap.Bool("has_encrypted_key", coll.EncryptedCollectionKey.Ciphertext != "" && coll.EncryptedCollectionKey.Nonce != ""),
			zap.String("collection_type", coll.CollectionType),
			zap.String("parent_id", coll.ParentID))
	}

	// Decrypt collection names and build result
	//
	// CIPHER DETECTION STRATEGY:
	// MapleFile supports two encryption schemes for backward compatibility:
	//
	// 1. XSalsa20-Poly1305 (libsodium secretbox) - Web frontend format
	//    - 24-byte nonce
	//    - Used by: web frontend (JavaScript with libsodium-wrappers)
	//    - Format: nonce (24 bytes) || ciphertext || auth tag (16 bytes)
	//
	// 2. ChaCha20-Poly1305 (IETF) - Legacy native app format
	//    - 12-byte nonce
	//    - Used by: older native desktop/CLI apps
	//    - Format: nonce (12 bytes) || ciphertext || auth tag (16 bytes)
	//
	// To determine which cipher was used for a collection, we check the
	// collection key's nonce size. The collection name is always encrypted
	// with the SAME cipher as the collection key, so we use the key nonce
	// size to decide how to split the encrypted name data.
	//
	a.logger.Info("Starting to decrypt collections",
		zap.Int("total_collections", len(apiResponse.Collections)),
		zap.Int("master_key_length", len(masterKey)))

	result := make([]*CollectionData, 0, len(apiResponse.Collections))
	for _, apiColl := range apiResponse.Collections {
		a.logger.Info("Processing collection",
			zap.String("collection_id", apiColl.ID),
			zap.Int("encrypted_name_base64_length", len(apiColl.EncryptedName)),
			zap.Int("encrypted_key_ciphertext_base64_length", len(apiColl.EncryptedCollectionKey.Ciphertext)),
			zap.Int("encrypted_key_nonce_base64_length", len(apiColl.EncryptedCollectionKey.Nonce)))

		// Step 1: Decode the collection key nonce FIRST to determine the cipher type.
		// The nonce size tells us which encryption algorithm was used:
		//   - 12 bytes = ChaCha20-Poly1305 (legacy native app)
		//   - 24 bytes = XSalsa20-Poly1305 (web frontend / current standard)
		//
		// Try multiple base64 encodings for compatibility:
		// - StdEncoding: standard base64 with padding (Go's default for []byte)
		// - RawStdEncoding: standard base64 without padding
		// - RawURLEncoding: URL-safe base64 without padding (libsodium default)
		keyNonce, err := base64.StdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Nonce)
		if err != nil {
			// Try URL-safe encoding without padding (libsodium format)
			keyNonce, err = base64.RawURLEncoding.DecodeString(apiColl.EncryptedCollectionKey.Nonce)
			if err != nil {
				// Try standard encoding without padding
				keyNonce, err = base64.RawStdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Nonce)
				if err != nil {
					a.logger.Error("Failed to decode collection key nonce (tried all encodings)",
						zap.String("collection_id", apiColl.ID),
						zap.String("nonce_value", apiColl.EncryptedCollectionKey.Nonce),
						zap.Error(err))
					continue
				}
			}
		}

		keyCiphertext, err := base64.StdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Ciphertext)
		if err != nil {
			// Try URL-safe encoding without padding
			keyCiphertext, err = base64.RawURLEncoding.DecodeString(apiColl.EncryptedCollectionKey.Ciphertext)
			if err != nil {
				// Try standard encoding without padding
				keyCiphertext, err = base64.RawStdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Ciphertext)
				if err != nil {
					a.logger.Error("Failed to decode collection key ciphertext (tried all encodings)",
						zap.String("collection_id", apiColl.ID),
						zap.Error(err))
					continue
				}
			}
		}

		a.logger.Info("Decoded collection key components",
			zap.String("collection_id", apiColl.ID),
			zap.Int("key_ciphertext_length", len(keyCiphertext)),
			zap.Int("key_nonce_length", len(keyNonce)))

		// Handle web frontend combined ciphertext format (nonce + encrypted_data)
		actualKeyCiphertext := extractActualCiphertext(keyCiphertext, keyNonce)
		if len(actualKeyCiphertext) != len(keyCiphertext) {
			a.logger.Info("Detected combined ciphertext format (web frontend), extracted encrypted data",
				zap.String("collection_id", apiColl.ID),
				zap.Int("original_length", len(keyCiphertext)),
				zap.Int("extracted_length", len(actualKeyCiphertext)))
		}

		// Decode the encrypted name from base64 (contains combined nonce + ciphertext)
		// Try multiple encodings for compatibility
		encryptedNameCombined, err := base64.StdEncoding.DecodeString(apiColl.EncryptedName)
		if err != nil {
			encryptedNameCombined, err = base64.RawURLEncoding.DecodeString(apiColl.EncryptedName)
			if err != nil {
				encryptedNameCombined, err = base64.RawStdEncoding.DecodeString(apiColl.EncryptedName)
				if err != nil {
					a.logger.Error("Failed to decode encrypted name (tried all encodings)",
						zap.String("collection_id", apiColl.ID),
						zap.Error(err))
					continue
				}
			}
		}

		a.logger.Info("Decoded encrypted name",
			zap.String("collection_id", apiColl.ID),
			zap.Int("decoded_length", len(encryptedNameCombined)))

		// Split the nonce and ciphertext from the combined encrypted name
		// Use the collection key nonce size to determine the cipher type for the name
		// (the name is encrypted with the same algorithm as the collection key)
		var nameNonce, nameCiphertext []byte
		if len(keyNonce) == 24 {
			// XSalsa20-Poly1305 (24-byte nonce) - web frontend format
			nameNonce, nameCiphertext, err = e2ee.SplitNonceAndCiphertextSecretBox(encryptedNameCombined)
		} else {
			// ChaCha20-Poly1305 (12-byte nonce) - legacy native format
			nameNonce, nameCiphertext, err = e2ee.SplitNonceAndCiphertext(encryptedNameCombined)
		}
		if err != nil {
			a.logger.Error("Failed to split encrypted name",
				zap.String("collection_id", apiColl.ID),
				zap.Int("combined_length", len(encryptedNameCombined)),
				zap.Int("key_nonce_length", len(keyNonce)),
				zap.Error(err))
			continue
		}

		a.logger.Info("Split encrypted name nonce and ciphertext",
			zap.String("collection_id", apiColl.ID),
			zap.Int("name_nonce_length", len(nameNonce)),
			zap.Int("name_ciphertext_length", len(nameCiphertext)))

		// Decrypt the collection key with the master key
		collectionKey, err := e2ee.DecryptCollectionKey(&e2ee.EncryptedKey{
			Ciphertext: actualKeyCiphertext,
			Nonce:      keyNonce,
		}, masterKey)
		if err != nil {
			a.logger.Error("Failed to decrypt collection key",
				zap.String("collection_id", apiColl.ID),
				zap.Int("key_nonce_length", len(keyNonce)),
				zap.Int("key_ciphertext_length", len(actualKeyCiphertext)),
				zap.Error(err))
			continue
		}

		a.logger.Info("Successfully decrypted collection key",
			zap.String("collection_id", apiColl.ID),
			zap.Int("collection_key_length", len(collectionKey)))

		// Decrypt the collection name with the collection key
		// Use auto-detection based on nonce size (12 = ChaCha20, 24 = XSalsa20)
		decryptedName, err := e2ee.DecryptWithAlgorithm(nameCiphertext, nameNonce, collectionKey)
		if err != nil {
			a.logger.Error("Failed to decrypt collection name",
				zap.String("collection_id", apiColl.ID),
				zap.Int("name_nonce_length", len(nameNonce)),
				zap.Int("name_ciphertext_length", len(nameCiphertext)),
				zap.Error(err))
			continue
		}

		// Decrypt custom icon if present
		var decryptedCustomIcon string
		if apiColl.EncryptedCustomIcon != "" {
			encryptedIconCombined, err := base64.StdEncoding.DecodeString(apiColl.EncryptedCustomIcon)
			if err != nil {
				a.logger.Warn("Failed to decode encrypted custom icon",
					zap.String("collection_id", apiColl.ID),
					zap.Error(err))
			} else {
				// Split nonce and ciphertext using the same algorithm as the collection key
				var iconNonce, iconCiphertext []byte
				if len(keyNonce) == 24 {
					iconNonce, iconCiphertext, err = e2ee.SplitNonceAndCiphertextSecretBox(encryptedIconCombined)
				} else {
					iconNonce, iconCiphertext, err = e2ee.SplitNonceAndCiphertext(encryptedIconCombined)
				}
				if err != nil {
					a.logger.Warn("Failed to split encrypted custom icon",
						zap.String("collection_id", apiColl.ID),
						zap.Error(err))
				} else {
					decryptedIcon, err := e2ee.DecryptWithAlgorithm(iconCiphertext, iconNonce, collectionKey)
					if err != nil {
						a.logger.Warn("Failed to decrypt custom icon",
							zap.String("collection_id", apiColl.ID),
							zap.Error(err))
					} else {
						decryptedCustomIcon = string(decryptedIcon)
					}
				}
			}
		}

		// Process embedded tags from the API response for this collection
		embeddedTags := make([]*EmbeddedTagData, 0, len(apiColl.Tags))
		for _, tagData := range apiColl.Tags {
			// Convert the embedded tag structure to client.Tag format for decryption
			clientTag := &client.Tag{
				ID:             tagData.ID,
				EncryptedName:  tagData.EncryptedName,
				EncryptedColor: tagData.EncryptedColor,
				EncryptedTagKey: &client.EncryptedTagKey{
					Ciphertext: tagData.EncryptedTagKey.Ciphertext,
					Nonce:      tagData.EncryptedTagKey.Nonce,
				},
			}

			// Decrypt the tag using the existing decryptTag helper
			decryptedTag, err := a.decryptTag(clientTag, masterKey)
			if err != nil {
				a.logger.Warn("Failed to decrypt embedded tag, skipping",
					zap.String("collection_id", apiColl.ID),
					zap.String("tag_id", tagData.ID),
					zap.Error(err))
				continue
			}

			embeddedTags = append(embeddedTags, &EmbeddedTagData{
				ID:    decryptedTag.ID,
				Name:  decryptedTag.Name,
				Color: decryptedTag.Color,
			})

			a.logger.Debug("Decrypted embedded tag for collection",
				zap.String("collection_id", apiColl.ID),
				zap.String("tag_id", decryptedTag.ID),
				zap.String("name", decryptedTag.Name),
				zap.String("color", decryptedTag.Color))
		}

		collectionData := &CollectionData{
			ID:             apiColl.ID,
			Name:           string(decryptedName),
			Description:    "", // TODO: Handle encrypted description if backend supports it
			CustomIcon:     decryptedCustomIcon,
			ParentID:       apiColl.ParentID,
			CollectionType: apiColl.CollectionType, // "folder" or "album"
			IsShared:       len(apiColl.SharedWith) > 0,
			TotalFiles:     apiColl.TotalFiles,
			CreatedAt:      apiColl.CreatedAt,
			ModifiedAt:     apiColl.ModifiedAt,
			Tags:           embeddedTags, // Use the decrypted embedded tags
		}

		a.logger.Info("Successfully decrypted collection",
			zap.String("collection_id", collectionData.ID),
			zap.String("name", collectionData.Name),
			zap.Bool("is_shared", collectionData.IsShared),
			zap.Int("total_files", collectionData.TotalFiles),
			zap.Int("tags_count", len(collectionData.Tags)))

		result = append(result, collectionData)
	}

	a.logger.Info("Listed and decrypted collections",
		zap.Int("count", len(result)),
		zap.Int("total_received", len(apiResponse.Collections)))

	// Log final result
	for _, coll := range result {
		a.logger.Info("Final collection state",
			zap.String("collection_id", coll.ID),
			zap.String("collection_name", coll.Name),
			zap.Int("final_tags_count", len(coll.Tags)))
	}

	return result, nil
}

// ListFilesByCollection returns all files in a collection
type CreateCollectionInput struct {
	Name           string   `json:"name"`
	Description    string   `json:"description,omitempty"`
	ParentID       string   `json:"parent_id,omitempty"`
	CustomIcon     string   `json:"custom_icon,omitempty"`     // Emoji or "icon:<id>" for predefined icons
	CollectionType string   `json:"collection_type,omitempty"` // "folder" or "album", defaults to "folder"
	TagIDs         []string `json:"tag_ids,omitempty"`         // Tag IDs to assign to this collection
}

// CreateCollectionResponse represents the response from creating a collection
type CreateCollectionResponse struct {
	CollectionID string `json:"collection_id"`
	Success      bool   `json:"success"`
}

// GetCollection returns a single collection by ID with decrypted key
func (a *Application) GetCollection(collectionID string) (*CollectionDetailData, error) {
	// Validate input
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return nil, err
	}

	// Get current session to get user's master key
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		a.logger.Error("Failed to get current session", zap.Error(err))
		return nil, fmt.Errorf("not authenticated: %w", err)
	}

	// Get the cached master key
	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()

	// Define a custom response struct that matches the actual backend API
	// (the client SDK's Collection struct is incorrect)
	type getCollectionAPIResponse struct {
		ID                     string `json:"id"`
		EncryptedName          string `json:"encrypted_name"`
		EncryptedCustomIcon    string `json:"encrypted_custom_icon,omitempty"`
		EncryptedCollectionKey struct {
			Ciphertext string `json:"ciphertext"`
			Nonce      string `json:"nonce"`
		} `json:"encrypted_collection_key"`
		CollectionType string    `json:"collection_type"`
		ParentID       string    `json:"parent_id,omitempty"`
		UserID         string    `json:"user_id"`
		CreatedAt      time.Time `json:"created_at"`
		ModifiedAt     time.Time `json:"modified_at"`
		TotalFiles     int       `json:"file_count"`
		Members        []struct {
			RecipientEmail string `json:"recipient_email"`
			RecipientID    string `json:"recipient_id"`
		} `json:"members"`
		Tags []struct {
			ID             string `json:"id"`
			EncryptedName  string `json:"encrypted_name"`
			EncryptedColor string `json:"encrypted_color"`
			EncryptedTagKey struct {
				Ciphertext string `json:"ciphertext"`
				Nonce      string `json:"nonce"`
			} `json:"encrypted_tag_key"`
		} `json:"tags,omitempty"`
	}

	// Make manual HTTP request to bypass broken SDK
	apiClient := a.authService.GetAPIClient()
	req, err := http.NewRequestWithContext(a.ctx, "GET", apiClient.GetBaseURL()+"/api/v1/collections/"+collectionID, nil)
	if err != nil {
		a.logger.Error("Failed to create HTTP request", zap.Error(err))
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Add authorization header
	req.Header.Set("Authorization", "Bearer "+session.AccessToken)
	req.Header.Set("Content-Type", "application/json")

	// Execute request
	resp, err := a.httpClient.Do(req)
	if err != nil {
		a.logger.Error("Failed to execute HTTP request", zap.Error(err))
		return nil, fmt.Errorf("failed to execute request: %w", err)
	}
	defer resp.Body.Close()

	// Check response status
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		a.logger.Error("Failed to get collection",
			zap.Int("status", resp.StatusCode),
			zap.String("body", string(body)))
		return nil, fmt.Errorf("API error: status %d", resp.StatusCode)
	}

	// Parse response
	var apiResponse getCollectionAPIResponse
	if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
		a.logger.Error("Failed to decode collection response", zap.Error(err))
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	a.logger.Debug("Processing collection for GetCollection",
		zap.String("collection_id", apiResponse.ID),
		zap.String("encrypted_name", apiResponse.EncryptedName))

	// Decode the encrypted name from base64 (contains combined nonce + ciphertext)
	encryptedNameCombined, err := base64.StdEncoding.DecodeString(apiResponse.EncryptedName)
	if err != nil {
		a.logger.Error("Failed to decode encrypted name", zap.Error(err))
		return nil, fmt.Errorf("failed to decode encrypted name: %w", err)
	}

	// Split the nonce and ciphertext from the combined encrypted name
	// Use auto-detection to handle both ChaCha20 (12-byte nonce) and XSalsa20 (24-byte nonce)
	nameNonce, nameCiphertext, err := e2ee.SplitNonceAndCiphertextAuto(encryptedNameCombined)
	if err != nil {
		a.logger.Error("Failed to split nonce and ciphertext from encrypted name", zap.Error(err))
		return nil, fmt.Errorf("failed to split encrypted name: %w", err)
	}

	// Decode the encrypted collection key (ciphertext and nonce separately)
	ciphertext, err := base64.StdEncoding.DecodeString(apiResponse.EncryptedCollectionKey.Ciphertext)
	if err != nil {
		a.logger.Error("Failed to decode encrypted collection key ciphertext", zap.Error(err))
		return nil, fmt.Errorf("failed to decode encrypted collection key ciphertext: %w", err)
	}

	nonce, err := base64.StdEncoding.DecodeString(apiResponse.EncryptedCollectionKey.Nonce)
	if err != nil {
		a.logger.Error("Failed to decode encrypted collection key nonce", zap.Error(err))
		return nil, fmt.Errorf("failed to decode encrypted collection key nonce: %w", err)
	}

	// Handle web frontend combined ciphertext format (nonce + encrypted_data)
	actualCiphertext := extractActualCiphertext(ciphertext, nonce)

	// Decrypt the collection key with the master key
	collectionKey, err := e2ee.DecryptCollectionKey(&e2ee.EncryptedKey{
		Ciphertext: actualCiphertext,
		Nonce:      nonce,
	}, masterKey)
	if err != nil {
		a.logger.Error("Failed to decrypt collection key", zap.Error(err))
		return nil, fmt.Errorf("failed to decrypt collection key: %w", err)
	}

	// Decrypt the collection name with the collection key
	// Use auto-detection based on nonce size (12 = ChaCha20, 24 = XSalsa20)
	decryptedName, err := e2ee.DecryptWithAlgorithm(nameCiphertext, nameNonce, collectionKey)
	if err != nil {
		a.logger.Error("Failed to decrypt collection name", zap.Error(err))
		return nil, fmt.Errorf("failed to decrypt collection name: %w", err)
	}

	// Decrypt custom icon if present
	var decryptedCustomIcon string
	if apiResponse.EncryptedCustomIcon != "" {
		encryptedIconCombined, err := base64.StdEncoding.DecodeString(apiResponse.EncryptedCustomIcon)
		if err != nil {
			a.logger.Warn("Failed to decode encrypted custom icon", zap.Error(err))
		} else {
			iconNonce, iconCiphertext, err := e2ee.SplitNonceAndCiphertextAuto(encryptedIconCombined)
			if err != nil {
				a.logger.Warn("Failed to split encrypted custom icon", zap.Error(err))
			} else {
				decryptedIcon, err := e2ee.DecryptWithAlgorithm(iconCiphertext, iconNonce, collectionKey)
				if err != nil {
					a.logger.Warn("Failed to decrypt custom icon", zap.Error(err))
				} else {
					decryptedCustomIcon = string(decryptedIcon)
				}
			}
		}
	}

	// Process embedded tags from the API response
	embeddedTags := make([]*EmbeddedTagData, 0, len(apiResponse.Tags))
	for _, tagData := range apiResponse.Tags {
		// Convert the embedded tag structure to client.Tag format for decryption
		clientTag := &client.Tag{
			ID:             tagData.ID,
			EncryptedName:  tagData.EncryptedName,
			EncryptedColor: tagData.EncryptedColor,
			EncryptedTagKey: &client.EncryptedTagKey{
				Ciphertext: tagData.EncryptedTagKey.Ciphertext,
				Nonce:      tagData.EncryptedTagKey.Nonce,
			},
		}

		// Decrypt the tag using the existing decryptTag helper
		decryptedTag, err := a.decryptTag(clientTag, masterKey)
		if err != nil {
			a.logger.Warn("Failed to decrypt embedded tag, skipping",
				zap.String("tag_id", tagData.ID),
				zap.Error(err))
			continue
		}

		embeddedTags = append(embeddedTags, &EmbeddedTagData{
			ID:    decryptedTag.ID,
			Name:  decryptedTag.Name,
			Color: decryptedTag.Color,
		})

		a.logger.Debug("Decrypted embedded tag",
			zap.String("tag_id", decryptedTag.ID),
			zap.String("name", decryptedTag.Name),
			zap.String("color", decryptedTag.Color))
	}

	// Build the response with decrypted data
	result := &CollectionDetailData{
		ID:             apiResponse.ID,
		Name:           string(decryptedName),
		Description:    "", // Description is not encrypted, but not in response
		CustomIcon:     decryptedCustomIcon,
		CollectionType: apiResponse.CollectionType,
		ParentID:       apiResponse.ParentID,
		IsShared:       len(apiResponse.Members) > 1,
		TotalFiles:     apiResponse.TotalFiles,
		CreatedAt:      apiResponse.CreatedAt.Format(time.RFC3339),
		ModifiedAt:     apiResponse.ModifiedAt.Format(time.RFC3339),
		CollectionKey:  base64.StdEncoding.EncodeToString(collectionKey),
		Tags:           embeddedTags,
	}

	a.logger.Info("Successfully retrieved and decrypted collection",
		zap.String("collection_id", collectionID),
		zap.String("name", result.Name),
		zap.Bool("is_shared", result.IsShared),
		zap.Int("total_files", result.TotalFiles),
		zap.Int("tags_count", len(result.Tags)))

	a.logger.Info("Collection tags detail",
		zap.String("collection_id", collectionID),
		zap.Any("tags", result.Tags))

	return result, nil
}

// CollectionDetailData represents detailed collection data including the decrypted key.
// This is returned when viewing a single collection's details.
//
// Similar to CollectionData, the TotalFiles field uses json:"file_count" to match
// the backend API's JSON field name. The frontend accesses this as collection.file_count.
type CollectionDetailData struct {
	ID             string             `json:"id"`
	Name           string             `json:"name"`
	Description    string             `json:"description,omitempty"`
	CustomIcon     string             `json:"custom_icon,omitempty"` // Decrypted custom icon (emoji or "icon:<id>")
	CollectionType string             `json:"collection_type"`       // "folder" or "album"
	ParentID       string             `json:"parent_id,omitempty"`
	IsShared       bool               `json:"is_shared"`
	TotalFiles     int                `json:"file_count"` // Maps to backend's "file_count" JSON field
	CreatedAt      string             `json:"created_at"`
	ModifiedAt     string             `json:"modified_at"`
	CollectionKey  string             `json:"collection_key"`  // Base64 encoded decrypted key for frontend
	Tags           []*EmbeddedTagData `json:"tags"`            // Tags assigned to this collection
}

// CreateCollection creates a new collection with E2EE.
// Uses XSalsa20-Poly1305 (SecretBox) for cross-client compatibility with web frontend.
func (a *Application) CreateCollection(input *CreateCollectionInput) (*CreateCollectionResponse, error) {
	// Validate input
	if err := inputvalidation.ValidateCollectionName(input.Name); err != nil {
		return nil, err
	}
	if input.ParentID != "" {
		if err := inputvalidation.ValidateCollectionID(input.ParentID); err != nil {
			return nil, fmt.Errorf("invalid parent ID: %w", err)
		}
	}
	if err := inputvalidation.ValidateDescription(input.Description); err != nil {
		return nil, err
	}

	// Get current session to get user's master key
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		a.logger.Error("Failed to get current session", zap.Error(err))
		return nil, fmt.Errorf("not authenticated: %w", err)
	}

	// Get the cached master key
	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 a new collection key (32 bytes for XSalsa20-Poly1305)
	collectionKey, err := e2ee.GenerateRandomBytes(32)
	if err != nil {
		a.logger.Error("Failed to generate collection key", zap.Error(err))
		return nil, fmt.Errorf("failed to generate collection key: %w", err)
	}

	// Encrypt the collection name with the collection key using XSalsa20-Poly1305 (SecretBox)
	// This produces 24-byte nonces compatible with web frontend's libsodium
	encryptedName, err := e2ee.EncryptWithSecretBox([]byte(input.Name), collectionKey)
	if err != nil {
		a.logger.Error("Failed to encrypt collection name", zap.Error(err))
		return nil, fmt.Errorf("failed to encrypt collection name: %w", err)
	}

	// Encrypt the collection key with the master key using XSalsa20-Poly1305 (SecretBox)
	encryptedCollectionKey, err := e2ee.EncryptCollectionKeySecretBox(collectionKey, masterKey)
	if err != nil {
		a.logger.Error("Failed to encrypt collection key", zap.Error(err))
		return nil, fmt.Errorf("failed to encrypt collection key: %w", err)
	}

	// Build the encrypted collection key structure for the API
	// IMPORTANT: The web frontend expects `ciphertext` to contain nonce + ciphertext combined
	// This matches the libsodium format where the nonce is prepended to the ciphertext
	combinedCollectionKeyCiphertext := e2ee.CombineNonceAndCiphertext(encryptedCollectionKey.Nonce, encryptedCollectionKey.Ciphertext)
	encryptedCollectionKeyStruct := map[string]string{
		"ciphertext": base64.StdEncoding.EncodeToString(combinedCollectionKeyCiphertext),
		"nonce":      base64.StdEncoding.EncodeToString(encryptedCollectionKey.Nonce),
	}

	// Generate a new collection ID
	collectionID := uuid.New().String()

	// Combine nonce and ciphertext for the encrypted name
	combinedEncryptedName := e2ee.CombineNonceAndCiphertext(encryptedName.Nonce, encryptedName.Ciphertext)

	// Determine collection type (default to "folder" if not specified)
	collectionType := input.CollectionType
	if collectionType == "" {
		collectionType = "folder"
	}

	// Build the raw API request manually
	apiRequest := map[string]interface{}{
		"id":                       collectionID,
		"encrypted_name":           base64.StdEncoding.EncodeToString(combinedEncryptedName),
		"collection_type":          collectionType,
		"encrypted_collection_key": encryptedCollectionKeyStruct,
	}

	if input.ParentID != "" {
		apiRequest["parent_id"] = input.ParentID
	}

	// Add tag IDs if provided
	if len(input.TagIDs) > 0 {
		apiRequest["tag_ids"] = input.TagIDs
		a.logger.Info("Creating collection with tags",
			zap.String("collection_id", collectionID),
			zap.Int("tag_count", len(input.TagIDs)))
	}

	// Encrypt and add custom icon if provided
	if input.CustomIcon != "" {
		encryptedIcon, err := e2ee.EncryptWithSecretBox([]byte(input.CustomIcon), collectionKey)
		if err != nil {
			a.logger.Error("Failed to encrypt custom icon", zap.Error(err))
			return nil, fmt.Errorf("failed to encrypt custom icon: %w", err)
		}
		combinedEncryptedIcon := e2ee.CombineNonceAndCiphertext(encryptedIcon.Nonce, encryptedIcon.Ciphertext)
		apiRequest["encrypted_custom_icon"] = base64.StdEncoding.EncodeToString(combinedEncryptedIcon)
	}

	// Call the API directly using doRequest
	apiClient := a.authService.GetAPIClient()

	// Define a custom response struct that matches the actual backend API
	// (the client SDK's Collection struct is incorrect)
	type collectionAPIResponse struct {
		ID                     string `json:"id"`
		EncryptedName          string `json:"encrypted_name"`
		EncryptedCollectionKey struct {
			Ciphertext string `json:"ciphertext"`
			Nonce      string `json:"nonce"`
		} `json:"encrypted_collection_key"`
		CollectionType string `json:"collection_type"`
		ParentID       string `json:"parent_id,omitempty"`
		UserID         string `json:"user_id"`
		CreatedAt      string `json:"created_at"`
	}

	// Marshal the request
	requestBody, err := json.Marshal(apiRequest)
	if err != nil {
		a.logger.Error("Failed to marshal collection request", zap.Error(err))
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Make the HTTP request
	req, err := http.NewRequestWithContext(a.ctx, "POST", apiClient.GetBaseURL()+"/api/v1/collections", bytes.NewReader(requestBody))
	if err != nil {
		a.logger.Error("Failed to create collection request", zap.Error(err))
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+session.AccessToken)

	resp, err := a.httpClient.Do(req)
	if err != nil {
		a.logger.Error("Failed to send collection request", zap.Error(err))
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		a.logger.Error("Failed to create collection",
			zap.Int("status", resp.StatusCode),
			zap.String("body", string(body)))
		return nil, fmt.Errorf("failed to create collection: %s", string(body))
	}

	var apiResponse collectionAPIResponse
	if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
		a.logger.Error("Failed to decode collection response", zap.Error(err))
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	a.logger.Info("Collection created successfully",
		zap.String("collection_id", apiResponse.ID),
		zap.String("name", input.Name))

	return &CreateCollectionResponse{
		CollectionID: apiResponse.ID,
		Success:      true,
	}, nil
}

// UpdateCollectionInput represents input for updating a collection
type UpdateCollectionInput struct {
	Name           string `json:"name"`
	CustomIcon     string `json:"custom_icon,omitempty"`     // Emoji or "icon:<id>" for predefined icons
	CollectionType string `json:"collection_type,omitempty"` // "folder" or "album"
}

// UpdateCollectionResponse represents the response from updating a collection
type UpdateCollectionResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message"`
}

// UpdateCollection updates a collection's encrypted name
func (a *Application) UpdateCollection(collectionID string, input *UpdateCollectionInput) (*UpdateCollectionResponse, error) {
	// Validate input
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return nil, err
	}
	if err := inputvalidation.ValidateCollectionName(input.Name); err != nil {
		return nil, err
	}

	// Get current session to get user's master key
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		a.logger.Error("Failed to get current session", zap.Error(err))
		return nil, fmt.Errorf("not authenticated: %w", err)
	}

	// Get the cached master key
	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()

	// First, we need to get the collection to decrypt its key
	apiClient := a.authService.GetAPIClient()

	// Make the HTTP request to get the collection
	getReq, err := http.NewRequestWithContext(a.ctx, "GET", apiClient.GetBaseURL()+"/api/v1/collections/"+collectionID, nil)
	if err != nil {
		a.logger.Error("Failed to create get collection request", zap.Error(err))
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	getReq.Header.Set("Authorization", "Bearer "+session.AccessToken)

	getResp, err := a.httpClient.Do(getReq)
	if err != nil {
		a.logger.Error("Failed to get collection", zap.Error(err))
		return nil, fmt.Errorf("failed to get collection: %w", err)
	}
	defer getResp.Body.Close()

	if getResp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(getResp.Body)
		a.logger.Error("Failed to get collection",
			zap.Int("status", getResp.StatusCode),
			zap.String("body", string(body)))
		return nil, fmt.Errorf("failed to get collection: %s", string(body))
	}

	// Parse the collection response to get the encrypted collection key and version
	var collectionResp struct {
		EncryptedCollectionKey struct {
			Ciphertext string `json:"ciphertext"`
			Nonce      string `json:"nonce"`
		} `json:"encrypted_collection_key"`
		Version uint64 `json:"version"`
	}
	if err := json.NewDecoder(getResp.Body).Decode(&collectionResp); err != nil {
		a.logger.Error("Failed to decode collection response", zap.Error(err))
		return nil, fmt.Errorf("failed to decode collection: %w", err)
	}

	// Decrypt the collection key
	keyNonce, err := base64.StdEncoding.DecodeString(collectionResp.EncryptedCollectionKey.Nonce)
	if err != nil {
		a.logger.Error("Failed to decode collection key nonce", zap.Error(err))
		return nil, fmt.Errorf("failed to decode key nonce: %w", err)
	}

	keyCiphertext, err := base64.StdEncoding.DecodeString(collectionResp.EncryptedCollectionKey.Ciphertext)
	if err != nil {
		a.logger.Error("Failed to decode collection key ciphertext", zap.Error(err))
		return nil, fmt.Errorf("failed to decode key ciphertext: %w", err)
	}

	// Handle web frontend combined ciphertext format (nonce + encrypted_data)
	actualKeyCiphertext := extractActualCiphertext(keyCiphertext, keyNonce)

	collectionKey, err := e2ee.DecryptCollectionKey(&e2ee.EncryptedKey{
		Ciphertext: actualKeyCiphertext,
		Nonce:      keyNonce,
	}, masterKey)
	if err != nil {
		a.logger.Error("Failed to decrypt collection key", zap.Error(err))
		return nil, fmt.Errorf("failed to decrypt collection key: %w", err)
	}

	// Encrypt the new name with the collection key using XSalsa20-Poly1305 (SecretBox)
	// This produces 24-byte nonces compatible with web frontend's libsodium
	encryptedName, err := e2ee.EncryptWithSecretBox([]byte(input.Name), collectionKey)
	if err != nil {
		a.logger.Error("Failed to encrypt collection name", zap.Error(err))
		return nil, fmt.Errorf("failed to encrypt collection name: %w", err)
	}

	// Combine nonce and ciphertext for the encrypted name
	combinedEncryptedName := e2ee.CombineNonceAndCiphertext(encryptedName.Nonce, encryptedName.Ciphertext)

	// Build the update request - backend requires encrypted_collection_key and version
	// The encrypted_collection_key must be sent as an object with ciphertext and nonce fields
	// using URL-safe base64 encoding (RawURLEncoding) to match the backend's custom unmarshaler
	updateRequest := map[string]interface{}{
		"encrypted_name": base64.StdEncoding.EncodeToString(combinedEncryptedName),
		"encrypted_collection_key": map[string]string{
			"ciphertext": collectionResp.EncryptedCollectionKey.Ciphertext,
			"nonce":      collectionResp.EncryptedCollectionKey.Nonce,
		},
		"version": collectionResp.Version,
	}

	// Encrypt and add custom icon (always include it - empty string means reset to default)
	if input.CustomIcon != "" {
		encryptedIcon, err := e2ee.EncryptWithSecretBox([]byte(input.CustomIcon), collectionKey)
		if err != nil {
			a.logger.Error("Failed to encrypt custom icon", zap.Error(err))
			return nil, fmt.Errorf("failed to encrypt custom icon: %w", err)
		}
		combinedEncryptedIcon := e2ee.CombineNonceAndCiphertext(encryptedIcon.Nonce, encryptedIcon.Ciphertext)
		updateRequest["encrypted_custom_icon"] = base64.StdEncoding.EncodeToString(combinedEncryptedIcon)
	} else {
		// Empty string clears the custom icon
		updateRequest["encrypted_custom_icon"] = ""
	}

	// Add collection type if specified
	if input.CollectionType != "" {
		updateRequest["collection_type"] = input.CollectionType
	}

	requestBody, err := json.Marshal(updateRequest)
	if err != nil {
		a.logger.Error("Failed to marshal update request", zap.Error(err))
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	// Make the HTTP PUT request to update the collection
	putReq, err := http.NewRequestWithContext(a.ctx, "PUT", apiClient.GetBaseURL()+"/api/v1/collections/"+collectionID, bytes.NewReader(requestBody))
	if err != nil {
		a.logger.Error("Failed to create update request", zap.Error(err))
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	putReq.Header.Set("Content-Type", "application/json")
	putReq.Header.Set("Authorization", "Bearer "+session.AccessToken)

	putResp, err := a.httpClient.Do(putReq)
	if err != nil {
		a.logger.Error("Failed to send update request", zap.Error(err))
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer putResp.Body.Close()

	if putResp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(putResp.Body)
		a.logger.Error("Failed to update collection",
			zap.Int("status", putResp.StatusCode),
			zap.String("body", string(body)))
		return nil, fmt.Errorf("failed to update collection: %s", string(body))
	}

	a.logger.Info("Collection updated successfully",
		zap.String("collection_id", collectionID),
		zap.String("new_name", input.Name))

	return &UpdateCollectionResponse{
		Success: true,
		Message: "Collection updated successfully",
	}, nil
}

// DeleteCollection soft-deletes a collection
func (a *Application) DeleteCollection(collectionID string) error {
	// Validate input
	if err := inputvalidation.ValidateCollectionID(collectionID); err != nil {
		return err
	}

	// Get current session for authentication
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		a.logger.Error("Failed to get current session", zap.Error(err))
		return fmt.Errorf("not authenticated: %w", err)
	}

	apiClient := a.authService.GetAPIClient()

	// Make the HTTP DELETE request
	req, err := http.NewRequestWithContext(a.ctx, "DELETE", apiClient.GetBaseURL()+"/api/v1/collections/"+collectionID, nil)
	if err != nil {
		a.logger.Error("Failed to create delete request", zap.Error(err))
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+session.AccessToken)

	resp, err := a.httpClient.Do(req)
	if err != nil {
		a.logger.Error("Failed to send delete request", zap.Error(err))
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// Accept both 200 OK and 204 No Content as success
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		body, _ := io.ReadAll(resp.Body)
		a.logger.Error("Failed to delete collection",
			zap.Int("status", resp.StatusCode),
			zap.String("body", string(body)))
		return fmt.Errorf("failed to delete collection: %s", string(body))
	}

	a.logger.Info("Collection deleted successfully", zap.String("collection_id", collectionID))
	return nil
}

// listSharedCollections fetches collections shared with the user
func (a *Application) listSharedCollections() ([]*CollectionData, error) {
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil {
		return nil, fmt.Errorf("not authenticated: %w", err)
	}

	masterKey, cleanup, err := a.keyCache.GetMasterKey(session.Email)
	if err != nil {
		return nil, fmt.Errorf("failed to get master key: %w", err)
	}
	defer cleanup()

	apiClient := a.authService.GetAPIClient()
	apiClient.SetTokens(session.AccessToken, session.RefreshToken)

	// Make request to shared collections endpoint
	type collectionAPIItem struct {
		ID                     string `json:"id"`
		EncryptedName          string `json:"encrypted_name"`
		EncryptedCollectionKey struct {
			Ciphertext string `json:"ciphertext"`
			Nonce      string `json:"nonce"`
		} `json:"encrypted_collection_key"`
		CollectionType string `json:"collection_type"`
		ParentID       string `json:"parent_id,omitempty"`
		TotalFiles     int    `json:"file_count"`
		CreatedAt      string `json:"created_at"`
		ModifiedAt     string `json:"modified_at"`
	}

	type listCollectionsResponse struct {
		Collections []*collectionAPIItem `json:"collections"`
	}

	req, err := http.NewRequestWithContext(a.ctx, "GET", apiClient.GetBaseURL()+"/api/v1/collections/shared", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	accessToken, _ := apiClient.GetTokens()
	req.Header.Set("Authorization", "Bearer "+accessToken)

	resp, err := a.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list shared collections: status %d", resp.StatusCode)
	}

	var apiResponse listCollectionsResponse
	if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	result := make([]*CollectionData, 0, len(apiResponse.Collections))
	for _, apiColl := range apiResponse.Collections {
		// Decrypt collection name
		keyCiphertext, err := base64.StdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Ciphertext)
		if err != nil {
			a.logger.Warn("Failed to decode collection key ciphertext", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}
		keyNonce, err := base64.StdEncoding.DecodeString(apiColl.EncryptedCollectionKey.Nonce)
		if err != nil {
			a.logger.Warn("Failed to decode collection key nonce", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}

		// Handle web frontend combined ciphertext format (nonce + encrypted_data)
		actualKeyCiphertext := extractActualCiphertext(keyCiphertext, keyNonce)

		collectionKey, err := e2ee.DecryptCollectionKey(&e2ee.EncryptedKey{
			Ciphertext: actualKeyCiphertext,
			Nonce:      keyNonce,
		}, masterKey)
		if err != nil {
			a.logger.Warn("Failed to decrypt collection key", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}

		encryptedNameBytes, err := base64.StdEncoding.DecodeString(apiColl.EncryptedName)
		if err != nil {
			a.logger.Warn("Failed to decode encrypted name", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}

		nameNonce, nameCiphertext, err := e2ee.SplitNonceAndCiphertextAuto(encryptedNameBytes)
		if err != nil {
			a.logger.Warn("Failed to split name nonce/ciphertext", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}

		decryptedName, err := e2ee.DecryptWithAlgorithm(nameCiphertext, nameNonce, collectionKey)
		if err != nil {
			a.logger.Warn("Failed to decrypt collection name", zap.String("id", apiColl.ID), zap.Error(err))
			continue
		}

		result = append(result, &CollectionData{
			ID:             apiColl.ID,
			Name:           string(decryptedName),
			ParentID:       apiColl.ParentID,
			CollectionType: apiColl.CollectionType,
			IsShared:       true,
			TotalFiles:     apiColl.TotalFiles,
			CreatedAt:      apiColl.CreatedAt,
			ModifiedAt:     apiColl.ModifiedAt,
		})
	}

	return result, nil
}

