package com.mosquito.project.integration;

import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.output.MigrateResult;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.testcontainers.containers.PostgreSQLContainer;

import javax.sql.DataSource;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
 * 审计日志不可篡改集成测试
 * 使用PostgreSQL容器验证数据库层防篡改约束
 * PRD要求: sys_audit_log表禁止UPDATE/DELETE，仅允许INSERT和SELECT
 */
@DisplayName("审计日志数据库层防篡改集成测试")
class AuditLogImmutabilityIntegrationTest {

    private PostgreSQLContainer<?> postgres;

    @AfterEach
    void tearDown() {
        if (postgres != null) {
            postgres.stop();
        }
    }

    @Test
    @DisplayName("INSERT操作应该成功")
    void insertShouldSucceed() {
        JdbcTemplate jdbcTemplate = migrateWithFlyway();

        // 插入测试数据应该成功
        int rowsAffected = jdbcTemplate.update(
            "INSERT INTO sys_audit_log (user_id, operation, resource_type, resource_id, prev_hash, event_hash, signature, sequence_num, timestamp, created_at) " +
            "VALUES (1, 'TEST_OP', 'TEST_RESOURCE', '123', '', 'abc123', 'sig1', 1, 1234567890, CURRENT_TIMESTAMP)"
        );

        assertThat(rowsAffected).isEqualTo(1);
    }

    @Test
    @DisplayName("UPDATE操作应该被数据库触发器阻止")
    void updateShouldBeBlocked() {
        JdbcTemplate jdbcTemplate = migrateWithFlyway();

        // 先插入一条记录
        jdbcTemplate.update(
            "INSERT INTO sys_audit_log (user_id, operation, resource_type, resource_id, prev_hash, event_hash, signature, sequence_num, timestamp, created_at) " +
            "VALUES (1, 'TEST_OP', 'TEST_RESOURCE', '123', '', 'abc123', 'sig1', 1, 1234567890, CURRENT_TIMESTAMP)"
        );

        // 尝试UPDATE应该被触发器阻止
        // PostgreSQL触发器抛出PSQLException，可能被包装为DataAccessException或直接抛出
        assertThatThrownBy(() ->
            jdbcTemplate.update("UPDATE sys_audit_log SET operation = 'MODIFIED' WHERE resource_id = '123'")
        )
        .isInstanceOfAny(
            org.springframework.dao.DataIntegrityViolationException.class,
            org.springframework.jdbc.BadSqlGrammarException.class,
            org.springframework.dao.DataAccessException.class,
            java.sql.SQLException.class
        )
        .hasMessageContaining("immutable")
        .hasMessageContaining("sys_audit_log");
    }

    @Test
    @DisplayName("DELETE操作应该被数据库触发器阻止")
    void deleteShouldBeBlocked() {
        JdbcTemplate jdbcTemplate = migrateWithFlyway();

        // 先插入一条记录
        Long id = jdbcTemplate.queryForObject(
            "INSERT INTO sys_audit_log (user_id, operation, resource_type, resource_id, prev_hash, event_hash, signature, sequence_num, timestamp, created_at) " +
            "VALUES (1, 'TEST_OP', 'TEST_RESOURCE', '456', '', 'def456', 'sig2', 2, 1234567891, CURRENT_TIMESTAMP) RETURNING id",
            Long.class
        );

        // 尝试DELETE应该被触发器阻止
        // PostgreSQL触发器抛出PSQLException，可能被包装为DataAccessException或直接抛出
        assertThatThrownBy(() ->
            jdbcTemplate.update("DELETE FROM sys_audit_log WHERE id = ?", id)
        )
        .isInstanceOfAny(
            org.springframework.dao.DataIntegrityViolationException.class,
            org.springframework.jdbc.BadSqlGrammarException.class,
            org.springframework.dao.DataAccessException.class,
            java.sql.SQLException.class
        )
        .hasMessageContaining("immutable")
        .hasMessageContaining("sys_audit_log");
    }

    @Test
    @DisplayName("SELECT操作应该正常工作")
    void selectShouldWork() {
        JdbcTemplate jdbcTemplate = migrateWithFlyway();

        // 插入测试数据
        jdbcTemplate.update(
            "INSERT INTO sys_audit_log (user_id, operation, resource_type, resource_id, prev_hash, event_hash, signature, sequence_num, timestamp, created_at) " +
            "VALUES (1, 'TEST_OP', 'TEST_RESOURCE', '789', '', 'ghi789', 'sig3', 3, 1234567892, CURRENT_TIMESTAMP)"
        );

        // SELECT应该正常工作
        Integer count = jdbcTemplate.queryForObject(
            "SELECT COUNT(*) FROM sys_audit_log WHERE resource_id = '789'",
            Integer.class
        );

        assertThat(count).isEqualTo(1);
    }

    @Test
    @DisplayName("触发器应在迁移后自动启用")
    void triggersShouldBeActiveAfterMigration() {
        JdbcTemplate jdbcTemplate = migrateWithFlyway();

        // 验证触发器函数存在
        Integer triggerCount = jdbcTemplate.queryForObject(
            "SELECT COUNT(*) FROM pg_trigger WHERE tgname IN ('trg_prevent_audit_log_update', 'trg_prevent_audit_log_delete')",
            Integer.class
        );

        assertThat(triggerCount).isEqualTo(2);
    }

    private JdbcTemplate migrateWithFlyway() {
        if (!isContainerRuntimeConfigured()) {
            if (isStrictMigrationMode()) {
                fail("严格模式下未检测到 Docker/Podman socket，禁止跳过 PostgreSQL 迁移验证");
            }
            Assumptions.assumeTrue(false, "未检测到 Docker/Podman socket，跳过 PostgreSQL 迁移验证");
        }

        postgres = new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("mosquito_audit_immutability_test")
            .withUsername("mosquito")
            .withPassword("mosquito");

        try {
            postgres.start();
        } catch (Throwable e) {
            if (isStrictMigrationMode()) {
                fail("严格模式下 PostgreSQL 容器启动失败: " + e.getMessage(), e);
            }
            Assumptions.assumeTrue(false, "无法启动 PostgreSQL 容器，跳过测试: " + e.getMessage());
        }

        DataSource dataSource = new DriverManagerDataSource(
            postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());

        Flyway flyway = Flyway.configure()
            .dataSource(dataSource)
            .locations("classpath:db/migration")
            .cleanDisabled(true)
            .load();
        MigrateResult result = flyway.migrate();
        assertThat(result.success).isTrue();

        return new JdbcTemplate(dataSource);
    }

    private boolean isContainerRuntimeConfigured() {
        String dockerHost = System.getenv("DOCKER_HOST");
        if (dockerHost != null && !dockerHost.isBlank()) {
            if (dockerHost.startsWith("unix://")) {
                Path socketPath = Path.of(dockerHost.substring("unix://".length()));
                return Files.exists(socketPath);
            }
            return true;
        }

        Path dockerSock = Path.of("/var/run/docker.sock");
        if (Files.exists(dockerSock)) {
            return true;
        }

        Path runUser = Path.of("/run/user");
        if (!Files.isDirectory(runUser)) {
            return false;
        }

        try (Stream<Path> userDirs = Files.list(runUser)) {
            return userDirs.anyMatch(dir -> Files.exists(dir.resolve("podman/podman.sock")));
        } catch (Exception e) {
            return false;
        }
    }

    private boolean isStrictMigrationMode() {
        return Boolean.parseBoolean(System.getProperty("migration.test.strict", "false"))
            || "true".equalsIgnoreCase(System.getenv("STRICT_MIGRATION_TESTS"));
    }
}
