package com.storycove.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Manages backup files on the filesystem.
 * The backup directory is the single source of truth for listing and restoring backups.
 * Files placed in the directory manually (e.g. copied from another server) are
 * automatically discovered and available for restore.
 */
@Service
public class BackupManagementService {

    private static final Logger logger = LoggerFactory.getLogger(BackupManagementService.class);

    @Value("${storycove.backup.dir:/app/backups}")
    private String backupDir;

    @Autowired
    private DatabaseManagementService databaseManagementService;

    @Autowired
    private LibraryService libraryService;

    /**
     * List all backup files for the current library, sorted newest first.
     * Discovers any .zip file in the library backup directory, including ones copied in from another server.
     */
    public List<BackupFileInfo> listBackups() throws IOException {
        String libraryId = currentLibraryId();
        Path dir = Paths.get(backupDir, libraryId);

        if (!Files.exists(dir)) {
            return List.of();
        }

        try (Stream<Path> stream = Files.list(dir)) {
            return stream
                    .filter(Files::isRegularFile)
                    .filter(p -> p.getFileName().toString().endsWith(".zip"))
                    .sorted(Comparator.comparing((Path p) -> {
                        try {
                            return Files.getLastModifiedTime(p);
                        } catch (IOException e) {
                            return null;
                        }
                    }).reversed())
                    .map(p -> {
                        try {
                            String filename = p.getFileName().toString();
                            long sizeBytes = Files.size(p);
                            Instant createdAt = Files.getLastModifiedTime(p).toInstant();
                            BackupOrigin origin = detectOrigin(filename);
                            return new BackupFileInfo(filename, sizeBytes, createdAt, origin);
                        } catch (IOException e) {
                            return null;
                        }
                    })
                    .filter(info -> info != null)
                    .collect(Collectors.toList());
        }
    }

    /**
     * Restore from a named backup file in the current library's backup directory.
     */
    public void restoreFromFile(String filename) throws IOException {
        validateFilename(filename);
        Path backupFile = resolveBackupFile(filename);

        if (!Files.exists(backupFile)) {
            throw new IOException("Backup file not found: " + filename);
        }

        logger.info("Restoring from backup file: {}", filename);
        try (var inputStream = Files.newInputStream(backupFile)) {
            databaseManagementService.restoreFromCompleteBackup(inputStream);
        } catch (java.sql.SQLException e) {
            throw new IOException("Restore failed: " + e.getMessage(), e);
        }
        logger.info("Restore from {} completed successfully", filename);
    }

    /**
     * Delete a named backup file from the current library's backup directory.
     */
    public void deleteBackup(String filename) throws IOException {
        validateFilename(filename);
        Path backupFile = resolveBackupFile(filename);

        if (!Files.exists(backupFile)) {
            throw new IOException("Backup file not found: " + filename);
        }

        Files.delete(backupFile);
        logger.info("Deleted backup file: {}", filename);
    }

    /**
     * Return a Resource for downloading a named backup file.
     */
    public Resource getBackupResource(String filename) throws IOException {
        validateFilename(filename);
        Path backupFile = resolveBackupFile(filename);

        if (!Files.exists(backupFile)) {
            throw new IOException("Backup file not found: " + filename);
        }

        return new FileSystemResource(backupFile);
    }

    private Path resolveBackupFile(String filename) {
        String libraryId = currentLibraryId();
        return Paths.get(backupDir, libraryId, filename);
    }

    private String currentLibraryId() {
        String libraryId = libraryService.getCurrentLibraryId();
        return libraryId != null ? libraryId : "default";
    }

    private BackupOrigin detectOrigin(String filename) {
        if (filename.startsWith("manual_backup_")) {
            return BackupOrigin.MANUAL;
        } else if (filename.startsWith("auto_backup_")) {
            return BackupOrigin.AUTO;
        }
        return BackupOrigin.UNKNOWN;
    }

    /** Prevent path traversal by rejecting filenames with directory separators. */
    private void validateFilename(String filename) throws IOException {
        if (filename == null || filename.isBlank() || filename.contains("/") || filename.contains("\\") || filename.contains("..")) {
            throw new IOException("Invalid backup filename: " + filename);
        }
        if (!filename.endsWith(".zip")) {
            throw new IOException("Only .zip backup files are supported");
        }
    }

    public enum BackupOrigin {
        MANUAL, AUTO, UNKNOWN
    }

    public static class BackupFileInfo {
        private final String filename;
        private final long sizeBytes;
        private final Instant createdAt;
        private final BackupOrigin origin;

        public BackupFileInfo(String filename, long sizeBytes, Instant createdAt, BackupOrigin origin) {
            this.filename = filename;
            this.sizeBytes = sizeBytes;
            this.createdAt = createdAt;
            this.origin = origin;
        }

        public String getFilename() {
            return filename;
        }

        public long getSizeBytes() {
            return sizeBytes;
        }

        public Instant getCreatedAt() {
            return createdAt;
        }

        public BackupOrigin getOrigin() {
            return origin;
        }
    }
}
