package app

import (
	"fmt"
	"os"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/file"
)

// =============================================================================
// FILE CLEANUP OPERATIONS
// =============================================================================

// DeleteFile soft-deletes a file from both the cloud and local storage
func (a *Application) DeleteFile(fileID string) error {
	a.logger.Info("DeleteFile called", zap.String("file_id", fileID))

	// Use the SDK client which has automatic token refresh on 401
	apiClient := a.authService.GetAPIClient()

	// Use SDK's DeleteFile method which has automatic 401 retry
	if err := apiClient.DeleteFile(a.ctx, fileID); err != nil {
		a.logger.Error("Failed to delete file from cloud",
			zap.String("file_id", fileID),
			zap.Error(err))
		return fmt.Errorf("failed to delete file: %w", err)
	}

	// Cloud delete succeeded - now clean up local data
	a.cleanupLocalFile(fileID)

	a.logger.Info("File deleted successfully", zap.String("file_id", fileID))
	return nil
}

// cleanupLocalFile removes physical binary files immediately and marks the metadata as deleted.
// The metadata record is kept for background cleanup later.
func (a *Application) cleanupLocalFile(fileID string) {
	// Get the local file record
	localFile, err := a.mustGetFileRepo().Get(fileID)
	if err != nil || localFile == nil {
		a.logger.Debug("No local file record to clean up", zap.String("file_id", fileID))
		return
	}

	// IMMEDIATELY delete physical binary files

	// Delete the physical decrypted file if it exists
	if localFile.FilePath != "" {
		if err := os.Remove(localFile.FilePath); err != nil {
			if !os.IsNotExist(err) {
				a.logger.Warn("Failed to delete local decrypted file",
					zap.String("file_id", fileID),
					zap.String("path", localFile.FilePath),
					zap.Error(err))
			}
		} else {
			a.logger.Info("Deleted local decrypted file",
				zap.String("file_id", fileID),
				zap.String("path", localFile.FilePath))
		}
	}

	// Delete the physical encrypted file if it exists
	if localFile.EncryptedFilePath != "" {
		if err := os.Remove(localFile.EncryptedFilePath); err != nil {
			if !os.IsNotExist(err) {
				a.logger.Warn("Failed to delete local encrypted file",
					zap.String("file_id", fileID),
					zap.String("path", localFile.EncryptedFilePath),
					zap.Error(err))
			}
		} else {
			a.logger.Info("Deleted local encrypted file",
				zap.String("file_id", fileID),
				zap.String("path", localFile.EncryptedFilePath))
		}
	}

	// Delete the thumbnail if it exists
	if localFile.ThumbnailPath != "" {
		if err := os.Remove(localFile.ThumbnailPath); err != nil {
			if !os.IsNotExist(err) {
				a.logger.Warn("Failed to delete local thumbnail",
					zap.String("file_id", fileID),
					zap.String("path", localFile.ThumbnailPath),
					zap.Error(err))
			}
		} else {
			a.logger.Info("Deleted local thumbnail",
				zap.String("file_id", fileID),
				zap.String("path", localFile.ThumbnailPath))
		}
	}

	// Mark the metadata record as deleted (will be cleaned up later by background process)
	// Clear the file paths since the physical files are now deleted
	localFile.State = file.StateDeleted
	localFile.FilePath = ""
	localFile.EncryptedFilePath = ""
	localFile.ThumbnailPath = ""
	localFile.ModifiedAt = time.Now()

	if err := a.mustGetFileRepo().Update(localFile); err != nil {
		a.logger.Warn("Failed to mark local file metadata as deleted",
			zap.String("file_id", fileID),
			zap.Error(err))
	} else {
		a.logger.Info("Marked local file metadata as deleted (will be cleaned up later)",
			zap.String("file_id", fileID))

		// Remove from search index
		if err := a.searchService.DeleteFile(fileID); err != nil {
			a.logger.Warn("Failed to remove file from search index",
				zap.String("file_id", fileID),
				zap.Error(err))
		}
	}
}

// purgeDeletedFileMetadata permanently removes a deleted file's metadata record.
// This is called by the background cleanup process after a retention period.
func (a *Application) purgeDeletedFileMetadata(fileID string) {
	if err := a.mustGetFileRepo().Delete(fileID); err != nil {
		a.logger.Warn("Failed to purge deleted file metadata",
			zap.String("file_id", fileID),
			zap.Error(err))
	} else {
		a.logger.Info("Purged deleted file metadata",
			zap.String("file_id", fileID))
	}
}

// deletedFileRetentionPeriod is how long to keep deleted file metadata before purging.
// This allows for potential recovery or sync conflict resolution.
const deletedFileRetentionPeriod = 7 * 24 * time.Hour // 7 days

// cleanupDeletedFiles runs in the background to clean up deleted files.
// It handles two cases:
// 1. Files marked as deleted that still have physical files (cleans up binaries immediately)
// 2. Files marked as deleted past the retention period (purges metadata)
func (a *Application) cleanupDeletedFiles() {
	a.logger.Info("Starting background cleanup of deleted files")

	// Get all local files
	localFiles, err := a.mustGetFileRepo().List()
	if err != nil {
		a.logger.Error("Failed to list local files for cleanup", zap.Error(err))
		return
	}

	binaryCleanedCount := 0
	metadataPurgedCount := 0
	now := time.Now()

	for _, localFile := range localFiles {
		// Only process deleted files
		if localFile.State != file.StateDeleted {
			continue
		}

		// Check if there are still physical files to clean up
		if localFile.FilePath != "" || localFile.EncryptedFilePath != "" || localFile.ThumbnailPath != "" {
			a.logger.Info("Cleaning up orphaned binary files for deleted record",
				zap.String("file_id", localFile.ID))
			a.cleanupLocalFile(localFile.ID)
			binaryCleanedCount++
			continue
		}

		// Check if metadata is past retention period and can be purged
		if now.Sub(localFile.ModifiedAt) > deletedFileRetentionPeriod {
			a.logger.Info("Purging deleted file metadata (past retention period)",
				zap.String("file_id", localFile.ID),
				zap.Time("deleted_at", localFile.ModifiedAt))
			a.purgeDeletedFileMetadata(localFile.ID)
			metadataPurgedCount++
		}
	}

	if binaryCleanedCount > 0 || metadataPurgedCount > 0 {
		a.logger.Info("Background cleanup completed",
			zap.Int("binaries_cleaned", binaryCleanedCount),
			zap.Int("metadata_purged", metadataPurgedCount))
	} else {
		a.logger.Debug("Background cleanup completed, no cleanup needed")
	}
}
