package com.usky.sas.util;

import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 数据库备份工具类
 */
@Slf4j
@Component
public class DatabaseBackupUtil {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DataSource dataSource;

    @Autowired(required = false)
    private com.usky.sas.config.BackupTableProperties tableProperties;

    private static final DateTimeFormatter FILENAME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
    private static final DateTimeFormatter SQL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 执行数据库备份
     *
     * @param backupType 备份类型：1-仅配置，2-配置 + 事件，3-完整备份
     * @param response HTTP 响应对象
     * @param customTableProperties 自定义表配置（可为 null，使用自动注入的配置）
     * @throws IOException IO 异常
     * @throws SQLException SQL 异常
     */
    public void backupDatabase(Integer backupType, javax.servlet.http.HttpServletResponse response, 
                               com.usky.sas.config.BackupTableProperties customTableProperties) throws IOException, SQLException {
        long startTime = System.currentTimeMillis();
        log.info("开始数据库备份，类型：{}", backupType);

        // 使用自定义配置或自动注入的配置
        com.usky.sas.config.BackupTableProperties props = 
            customTableProperties != null ? customTableProperties : this.tableProperties;
        
        // 如果配置为空，使用默认配置
        if (props == null) {
            props = createDefaultBackupProperties();
        }

        // 创建临时目录
        Path tempDir = Files.createTempDirectory("backup_");
        PrintWriter writer = null;

        try {
            // 准备 SQL 文件
            Path sqlFilePath = tempDir.resolve("backup.sql");
            writer = new PrintWriter(new FileWriter(sqlFilePath.toFile()));

            // 根据备份类型获取需要备份的表列表
            List<String> tables = getTablesForBackup(backupType, props);
            log.info("需要备份的表数量：{}", tables.size());

            // 写入备份头信息
            writer.println("-- SAS System Database Backup");
            writer.println("-- Backup Type: " + backupType);
            writer.println("-- Backup Time: " + LocalDateTime.now().format(SQL_DATE_FORMATTER));
            writer.println("-- Database: " + dataSource.getConnection().getCatalog());
            writer.println();

            // 备份每个表的结构和数据
            int tableCount = 0;
            for (String table : tables) {
                try {
                    backupTable(writer, table);
                    tableCount++;
                    if (tableCount % 10 == 0) {
                        log.info("已备份 {}/{} 个表", tableCount, tables.size());
                    }
                } catch (Exception e) {
                    log.error("备份表 {} 失败：{}", table, e.getMessage());
                    throw e;
                }
            }

            writer.flush();
            log.info("数据库表备份完成，共备份 {} 个表", tableCount);

            // 创建 ZIP 压缩文件
            Path zipFilePath = createZipFile(tempDir, sqlFilePath, backupType, tableProperties);
            
            // 设置响应头并输出文件
            setupResponse(response, zipFilePath, backupType);
            
            long duration = System.currentTimeMillis() - startTime;
            log.info("数据库备份完成，耗时：{} 秒，文件大小：{} bytes", 
                    duration / 1000.0, Files.size(zipFilePath));

        } catch (Exception e) {
            log.error("数据库备份失败：{}", e.getMessage(), e);
            throw new RuntimeException("数据库备份失败：" + e.getMessage(), e);
        } finally {
            // 关闭资源
            if (writer != null) {
                writer.close();
            }
            // 延迟删除临时目录
            deleteDirectoryLater(tempDir, tableProperties.getTempDirRetentionMs());
        }
    }

    /**
     * 获取需要备份的表列表
     */
    private List<String> getTablesForBackup(Integer backupType, 
                                            com.usky.sas.config.BackupTableProperties tableProperties) {
        List<String> allTables = getAllTables();
        
        if (backupType == 1) {
            // 类型 1：排除事件表
            List<String> excludeTables = tableProperties.getExclude();
            if (excludeTables != null && !excludeTables.isEmpty()) {
                allTables.removeAll(excludeTables);
            }
        } else if (backupType == 2 || backupType == 3) {
            // 类型 2 和 3：包含所有表（使用 includeEvent 列表）
            List<String> includeTables = tableProperties.getIncludeEvent();
            if (includeTables != null && !includeTables.isEmpty()) {
                allTables.retainAll(includeTables);
            }
        }
        
        return allTables;
    }

    /**
     * 获取数据库中所有表
     */
    private List<String> getAllTables() {
        String sql = "SHOW TABLES";
        return jdbcTemplate.queryForList(sql, String.class);
    }

    /**
     * 备份单个表
     */
    private void backupTable(PrintWriter writer, String tableName) throws SQLException {
        log.debug("备份表：{}", tableName);

        // 获取建表语句
        String createTableSql = getCreateTableSql(tableName);
        
        // 写入 DROP 语句
        writer.println("DROP TABLE IF EXISTS `" + tableName + "`;");
        
        // 写入建表语句
        writer.println(createTableSql + ";");
        
        // 获取表数据
        List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT * FROM " + tableName);
        
        if (!rows.isEmpty()) {
            // 生成 INSERT 语句
            for (Map<String, Object> row : rows) {
                String insertSql = generateInsertSql(tableName, row);
                writer.println(insertSql);
            }
            writer.println();
        }
    }

    /**
     * 获取建表语句
     */
    private String getCreateTableSql(String tableName) {
        return jdbcTemplate.queryForObject("SHOW CREATE TABLE " + tableName, (rs, rowNum) -> rs.getString(2));
    }

    /**
     * 生成 INSERT 语句
     */
    private String generateInsertSql(String tableName, Map<String, Object> row) {
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();

        for (Map.Entry<String, Object> entry : row.entrySet()) {
            String columnName = entry.getKey();
            Object value = entry.getValue();

            if (columns.length() > 0) {
                columns.append(", ");
                values.append(", ");
            }

            columns.append("`").append(columnName).append("`");

            if (value == null) {
                values.append("NULL");
            } else if (value instanceof Number) {
                values.append(value);
            } else if (value instanceof java.util.Date) {
                values.append("'").append(
                    new java.sql.Timestamp(((java.util.Date) value).getTime())
                        .toString()
                ).append("'");
            } else {
                // 字符串转义
                String escapedValue = value.toString()
                    .replace("\\", "\\\\")
                    .replace("'", "\\'")
                    .replace("\n", "\\n")
                    .replace("\r", "\\r");
                values.append("'").append(escapedValue).append("'");
            }
        }

        return String.format("INSERT INTO `%s` (%s) VALUES (%s);", 
                tableName, columns, values);
    }

    /**
     * 创建 ZIP 压缩文件
     */
    private Path createZipFile(Path tempDir, Path sqlFilePath, Integer backupType,
                               com.usky.sas.config.BackupTableProperties tableProperties) throws Exception {
        Path zipFilePath = tempDir.resolve("backup.zip");
        
        try (ZipFile zipFile = new ZipFile(zipFilePath.toString(), 
                tableProperties.getPassword() != null ? tableProperties.getPassword().toCharArray() : "backup123".toCharArray())) {
            
            ZipParameters parameters = new ZipParameters();
            parameters.setEncryptionMethod(EncryptionMethod.AES);
            parameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
            parameters.setEncryptFiles(true);

            // 添加 SQL 文件
            zipFile.addFile(sqlFilePath.toString(), parameters);

            // 如果是完整备份（类型 3），添加图片文件
            if (backupType == 3) {
                addUploadDirectoryToZip(zipFile, parameters, tableProperties != null ? tableProperties.getUploadDir() : null);
            }

            return zipFilePath;
        }
    }

    /**
     * 创建默认备份配置
     */
    private com.usky.sas.config.BackupTableProperties createDefaultBackupProperties() {
        com.usky.sas.config.BackupTableProperties props = new com.usky.sas.config.BackupTableProperties();
        
        // 默认排除的表（事件表）
        List<String> exclude = Arrays.asList(
            "sas_alarm_event",
            "sas_acquisition_event",
            "sas_entrance_event",
            "sas_collection_event",
            "sas_gauth_event",
            "sas_parking_event",
            "sas_patrol_event",
            "sas_perception_event",
            "sas_roadblock_event",
            "sas_snap_event",
            "sas_usb_event",
            "sas_video_event"
        );
        props.setExclude(exclude);
        
        // 默认包含的表（所有表）
        props.setIncludeEvent(new ArrayList<>());
        
        // 默认上传目录
        props.setUploadDir(System.getProperty("java.io.tmpdir") + "/upload");
        
        // 默认密码
        props.setPassword("backup123");
        
        return props;
    }

    /**
     * 添加上传目录到 ZIP
     */
    private void addUploadDirectoryToZip(ZipFile zipFile, ZipParameters parameters, String uploadDir) throws IOException {
        if (uploadDir == null || uploadDir.isEmpty()) {
            log.warn("上传目录未配置，跳过图片备份");
            return;
        }

        Path uploadSource = Paths.get(uploadDir);
        Path uploadTarget = Paths.get(uploadSource.toString() + "_backup");

        if (Files.exists(uploadSource)) {
            try {
                // 复制目录到临时位置
                copyDirectory(uploadSource, uploadTarget);
                
                // 添加到 ZIP
                File uploadTargetFile = uploadTarget.toFile();
                if (uploadTargetFile.exists()) {
                    zipFile.addFolder(uploadTargetFile, parameters);
                    log.info("已添加上传目录到备份：{}", uploadDir);
                }
            } finally {
                // 删除临时复制的目录
                deleteDirectory(uploadTarget);
            }
        } else {
            log.warn("上传目录不存在：{}", uploadDir);
        }
    }

    /**
     * 复制目录
     */
    private void copyDirectory(Path source, Path target) throws IOException {
        Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Path targetDir = target.resolve(source.relativize(dir));
                if (!Files.exists(targetDir)) {
                    Files.createDirectory(targetDir);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.copy(file, target.resolve(source.relativize(file)), 
                        StandardCopyOption.REPLACE_EXISTING);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 设置 HTTP 响应
     */
    private void setupResponse(javax.servlet.http.HttpServletResponse response, 
                               Path zipFilePath, Integer backupType) throws IOException {
        String backupTypeName;
        switch (backupType) {
            case 1:
                backupTypeName = "配置";
                break;
            case 2:
                backupTypeName = "配置 + 事件";
                break;
            case 3:
                backupTypeName = "完整备份";
                break;
            default:
                backupTypeName = "备份";
        }

        String timestamp = LocalDateTime.now().format(FILENAME_FORMATTER);
        String filename = URLEncoder.encode(
            "数据备份文件(" + backupTypeName + ")_" + timestamp + ".zip", 
            "UTF-8"
        );

        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + filename);
        response.setContentLengthLong(Files.size(zipFilePath));

        try (InputStream inputStream = Files.newInputStream(zipFilePath);
             OutputStream outputStream = response.getOutputStream()) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();
        }
    }

    /**
     * 延迟删除目录
     */
    private void deleteDirectoryLater(Path directory, long delayMs) {
        ExecutorService executor = java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        });

        executor.submit(() -> {
            try {
                Thread.sleep(delayMs);
                deleteDirectory(directory);
                log.info("已删除临时目录：{}", directory);
            } catch (Exception e) {
                log.error("删除临时目录失败：{}", directory, e);
            }
        });

        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.MINUTES)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 删除目录
     */
    private void deleteDirectory(Path directory) throws IOException {
        if (Files.exists(directory)) {
            Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}
