// monorepo/cloud/maplefile-backend/internal/maplefile/repo/filemetadata/update.go
package filemetadata

import (
	"context"
	"fmt"
	"time"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

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

func (impl *fileMetadataRepositoryImpl) Update(file *dom_file.File) error {
	if file == nil {
		return fmt.Errorf("file cannot be nil")
	}

	if !impl.isValidUUID(file.ID) {
		return fmt.Errorf("file ID is required")
	}

	// Get existing file to compare changes
	existing, err := impl.Get(file.ID)
	if err != nil {
		return fmt.Errorf("failed to get existing file: %w", err)
	}

	if existing == nil {
		return fmt.Errorf("file not found")
	}

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

	// Serialize encrypted file key
	encryptedKeyJSON, err := impl.serializeEncryptedFileKey(file.EncryptedFileKey)
	if err != nil {
		return fmt.Errorf("failed to serialize encrypted file key: %w", err)
	}

	// Serialize tags
	tagsJSON, err := impl.serializeTags(file.Tags)
	if err != nil {
		return fmt.Errorf("failed to serialize tags: %w", err)
	}

	batch := impl.Session.NewBatch(gocql.LoggedBatch)

	// 1. Update main table
	batch.Query(`UPDATE maplefile.files_by_id SET
		collection_id = ?, owner_id = ?, encrypted_metadata = ?, encrypted_file_key = ?,
		encryption_version = ?, encrypted_hash = ?, encrypted_file_object_key = ?,
		encrypted_file_size_in_bytes = ?, encrypted_thumbnail_object_key = ?,
		encrypted_thumbnail_size_in_bytes = ?, tags = ?, created_at = ?, created_by_user_id = ?,
		modified_at = ?, modified_by_user_id = ?, version = ?, state = ?,
		tombstone_version = ?, tombstone_expiry = ?
		WHERE id = ?`,
		file.CollectionID, file.OwnerID, file.EncryptedMetadata, encryptedKeyJSON,
		file.EncryptionVersion, file.EncryptedHash, file.EncryptedFileObjectKey,
		file.EncryptedFileSizeInBytes, file.EncryptedThumbnailObjectKey,
		file.EncryptedThumbnailSizeInBytes, tagsJSON, file.CreatedAt, file.CreatedByUserID,
		file.ModifiedAt, file.ModifiedByUserID, file.Version, file.State,
		file.TombstoneVersion, file.TombstoneExpiry, file.ID)

	// 2. Update collection table - delete old entry and insert new one
	if existing.CollectionID != file.CollectionID || existing.ModifiedAt != file.ModifiedAt {
		batch.Query(`DELETE FROM maplefile.files_by_collection
			WHERE collection_id = ? AND modified_at = ? AND id = ?`,
			existing.CollectionID, existing.ModifiedAt, file.ID)

		batch.Query(`INSERT INTO maplefile.files_by_collection
			(collection_id, modified_at, id, owner_id, encrypted_metadata, encrypted_file_key,
			 encryption_version, encrypted_hash, encrypted_file_object_key, encrypted_file_size_in_bytes,
			 encrypted_thumbnail_object_key, encrypted_thumbnail_size_in_bytes,
			 created_at, created_by_user_id, modified_by_user_id, version,
			 state, tombstone_version, tombstone_expiry)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			file.CollectionID, file.ModifiedAt, file.ID, file.OwnerID, file.EncryptedMetadata,
			encryptedKeyJSON, file.EncryptionVersion, file.EncryptedHash, file.EncryptedFileObjectKey,
			file.EncryptedFileSizeInBytes, file.EncryptedThumbnailObjectKey,
			file.EncryptedThumbnailSizeInBytes, file.CreatedAt, file.CreatedByUserID,
			file.ModifiedByUserID, file.Version, file.State, file.TombstoneVersion, file.TombstoneExpiry)
	}

	// 3. Update owner table - delete old entry and insert new one
	if existing.OwnerID != file.OwnerID || existing.ModifiedAt != file.ModifiedAt {
		batch.Query(`DELETE FROM maplefile.files_by_owner
			WHERE owner_id = ? AND modified_at = ? AND id = ?`,
			existing.OwnerID, existing.ModifiedAt, file.ID)

		batch.Query(`INSERT INTO maplefile.files_by_owner
			(owner_id, modified_at, id, collection_id, encrypted_metadata, encrypted_file_key,
			 encryption_version, encrypted_hash, encrypted_file_object_key, encrypted_file_size_in_bytes,
			 encrypted_thumbnail_object_key, encrypted_thumbnail_size_in_bytes,
			 created_at, created_by_user_id, modified_by_user_id, version,
			 state, tombstone_version, tombstone_expiry)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			file.OwnerID, file.ModifiedAt, file.ID, file.CollectionID, file.EncryptedMetadata,
			encryptedKeyJSON, file.EncryptionVersion, file.EncryptedHash, file.EncryptedFileObjectKey,
			file.EncryptedFileSizeInBytes, file.EncryptedThumbnailObjectKey,
			file.EncryptedThumbnailSizeInBytes, file.CreatedAt, file.CreatedByUserID,
			file.ModifiedByUserID, file.Version, file.State, file.TombstoneVersion, file.TombstoneExpiry)
	}

	// 4. Update created_by table - only if creator changed (rare) or created date changed
	if existing.CreatedByUserID != file.CreatedByUserID || existing.CreatedAt != file.CreatedAt {
		batch.Query(`DELETE FROM maplefile.files_by_creator
			WHERE created_by_user_id = ? AND created_at = ? AND id = ?`,
			existing.CreatedByUserID, existing.CreatedAt, file.ID)

		batch.Query(`INSERT INTO maplefile.files_by_creator
			(created_by_user_id, created_at, id, collection_id, owner_id, encrypted_metadata,
			 encrypted_file_key, encryption_version, encrypted_hash, encrypted_file_object_key,
			 encrypted_file_size_in_bytes, encrypted_thumbnail_object_key, encrypted_thumbnail_size_in_bytes,
			 modified_at, modified_by_user_id, version, state, tombstone_version, tombstone_expiry)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			file.CreatedByUserID, file.CreatedAt, file.ID, file.CollectionID, file.OwnerID,
			file.EncryptedMetadata, encryptedKeyJSON, file.EncryptionVersion, file.EncryptedHash,
			file.EncryptedFileObjectKey, file.EncryptedFileSizeInBytes, file.EncryptedThumbnailObjectKey,
			file.EncryptedThumbnailSizeInBytes, file.ModifiedAt, file.ModifiedByUserID, file.Version,
			file.State, file.TombstoneVersion, file.TombstoneExpiry)
	}

	// 5. Update user sync table - delete old entry and insert new one for owner
	batch.Query(`DELETE FROM maplefile.files_by_user
		WHERE user_id = ? AND modified_at = ? AND id = ?`,
		existing.OwnerID, existing.ModifiedAt, file.ID)

	batch.Query(`INSERT INTO maplefile.files_by_user
		(user_id, modified_at, id, collection_id, owner_id, encrypted_metadata,
		 encrypted_file_key, encryption_version, encrypted_hash, encrypted_file_object_key,
		 encrypted_file_size_in_bytes, encrypted_thumbnail_object_key, encrypted_thumbnail_size_in_bytes,
		 tags, created_at, created_by_user_id, modified_by_user_id, version,
		 state, tombstone_version, tombstone_expiry)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		file.OwnerID, file.ModifiedAt, file.ID, file.CollectionID, file.OwnerID,
		file.EncryptedMetadata, encryptedKeyJSON, file.EncryptionVersion, file.EncryptedHash,
		file.EncryptedFileObjectKey, file.EncryptedFileSizeInBytes, file.EncryptedThumbnailObjectKey,
		file.EncryptedThumbnailSizeInBytes, tagsJSON, file.CreatedAt, file.CreatedByUserID,
		file.ModifiedByUserID, file.Version, file.State, file.TombstoneVersion, file.TombstoneExpiry)

	// 6. Update denormalized files_by_tag_id table
	// Calculate tag changes
	oldTagsMap := make(map[gocql.UUID]bool)
	for _, tag := range existing.Tags {
		oldTagsMap[tag.ID] = true
	}

	newTagsMap := make(map[gocql.UUID]bool)
	for _, tag := range file.Tags {
		newTagsMap[tag.ID] = true
	}

	// Delete entries for removed tags
	for tagID := range oldTagsMap {
		if !newTagsMap[tagID] {
			impl.Logger.Debug("removing file from tag denormalized table",
				zap.String("file_id", file.ID.String()),
				zap.String("tag_id", tagID.String()))
			batch.Query(`DELETE FROM maplefile.files_by_tag_id
				WHERE tag_id = ? AND file_id = ?`,
				tagID, file.ID)
		}
	}

	// Insert/Update entries for current tags
	for _, tag := range file.Tags {
		impl.Logger.Debug("updating file in tag denormalized table",
			zap.String("file_id", file.ID.String()),
			zap.String("tag_id", tag.ID.String()))

		batch.Query(`INSERT INTO maplefile.files_by_tag_id
			(tag_id, file_id, collection_id, owner_id, encrypted_metadata, encrypted_file_key,
			 encryption_version, encrypted_hash, encrypted_file_object_key,
			 encrypted_file_size_in_bytes, encrypted_thumbnail_object_key,
			 encrypted_thumbnail_size_in_bytes, tag_ids, created_at, created_by_user_id,
			 modified_at, modified_by_user_id, version, state, tombstone_version, tombstone_expiry,
			 created_from_ip_address, modified_from_ip_address, ip_anonymized_at)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			tag.ID, file.ID, file.CollectionID, file.OwnerID, file.EncryptedMetadata,
			encryptedKeyJSON, file.EncryptionVersion, file.EncryptedHash,
			file.EncryptedFileObjectKey, file.EncryptedFileSizeInBytes,
			file.EncryptedThumbnailObjectKey, file.EncryptedThumbnailSizeInBytes,
			tagsJSON, file.CreatedAt, file.CreatedByUserID, file.ModifiedAt,
			file.ModifiedByUserID, file.Version, file.State, file.TombstoneVersion,
			file.TombstoneExpiry,
			nil, nil, nil) // IP tracking fields not yet in domain model
	}

	// Execute batch
	if err := impl.Session.ExecuteBatch(batch); err != nil {
		impl.Logger.Error("failed to update file",
			zap.String("file_id", file.ID.String()),
			zap.Error(err))
		return fmt.Errorf("failed to update file: %w", err)
	}

	// Handle file count updates based on state changes
	wasActive := existing.State == dom_file.FileStateActive
	isActive := file.State == dom_file.FileStateActive

	// Handle collection change for active files
	if existing.CollectionID != file.CollectionID && wasActive && isActive {
		// File moved from one collection to another while remaining active
		// Decrement old collection count
		if err := impl.CollectionRepo.DecrementFileCount(context.Background(), existing.CollectionID); err != nil {
			impl.Logger.Error("failed to decrement old collection file count",
				zap.String("file_id", file.ID.String()),
				zap.String("collection_id", existing.CollectionID.String()),
				zap.Error(err))
			// Don't fail the entire operation if count update fails
		}
		// Increment new collection count
		if err := impl.CollectionRepo.IncrementFileCount(context.Background(), file.CollectionID); err != nil {
			impl.Logger.Error("failed to increment new collection file count",
				zap.String("file_id", file.ID.String()),
				zap.String("collection_id", file.CollectionID.String()),
				zap.Error(err))
			// Don't fail the entire operation if count update fails
		}
	} else if wasActive && !isActive {
		// File transitioned from active to non-active (e.g., deleted)
		if err := impl.CollectionRepo.DecrementFileCount(context.Background(), existing.CollectionID); err != nil {
			impl.Logger.Error("failed to decrement collection file count",
				zap.String("file_id", file.ID.String()),
				zap.String("collection_id", existing.CollectionID.String()),
				zap.Error(err))
			// Don't fail the entire operation if count update fails
		}
	} else if !wasActive && isActive {
		// File transitioned from non-active to active (e.g., restored)
		if err := impl.CollectionRepo.IncrementFileCount(context.Background(), file.CollectionID); err != nil {
			impl.Logger.Error("failed to increment collection file count",
				zap.String("file_id", file.ID.String()),
				zap.String("collection_id", file.CollectionID.String()),
				zap.Error(err))
			// Don't fail the entire operation if count update fails
		}
	}

	impl.Logger.Info("file updated successfully",
		zap.String("file_id", file.ID.String()))

	return nil
}
