package com.cameleer.server.app.storage;

import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditRecord;
import com.cameleer.server.core.admin.AuditRepository;
import com.cameleer.server.core.admin.AuditResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Repository
public class PostgresAuditRepository implements AuditRepository {

    private static final Set<String> ALLOWED_SORT_COLUMNS =
            Set.of("timestamp", "username", "action", "category");
    private static final int MAX_PAGE_SIZE = 100;

    private final JdbcTemplate jdbc;
    private final ObjectMapper objectMapper;

    public PostgresAuditRepository(JdbcTemplate jdbc, ObjectMapper objectMapper) {
        this.jdbc = jdbc;
        this.objectMapper = objectMapper;
    }

    @Override
    public void insert(AuditRecord auditRecord) {
        String detailJson = null;
        if (auditRecord.detail() != null) {
            try {
                detailJson = objectMapper.writeValueAsString(auditRecord.detail());
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Failed to serialize audit detail", e);
            }
        }

        jdbc.update("""
                INSERT INTO audit_log (username, action, category, target, detail, result, ip_address, user_agent)
                VALUES (?, ?, ?, ?, ?::jsonb, ?, ?, ?)
                """,
                auditRecord.username(), auditRecord.action(),
                auditRecord.category() != null ? auditRecord.category().name() : null,
                auditRecord.target(), detailJson,
                auditRecord.result() != null ? auditRecord.result().name() : null,
                auditRecord.ipAddress(), auditRecord.userAgent());
    }

    @Override
    public AuditPage find(AuditQuery query) {
        int pageSize = Math.min(query.size() > 0 ? query.size() : 20, MAX_PAGE_SIZE);
        int offset = query.page() * pageSize;

        StringBuilder where = new StringBuilder("WHERE timestamp >= ? AND timestamp <= ?");
        List<Object> params = new ArrayList<>();
        params.add(Timestamp.from(query.from()));
        params.add(Timestamp.from(query.to()));

        if (query.username() != null && !query.username().isBlank()) {
            where.append(" AND username = ?");
            params.add(query.username());
        }
        if (query.category() != null) {
            where.append(" AND category = ?");
            params.add(query.category().name());
        }
        if (query.search() != null && !query.search().isBlank()) {
            where.append(" AND (action ILIKE ? OR target ILIKE ?)");
            String like = "%" + query.search() + "%";
            params.add(like);
            params.add(like);
        }

        // Count query
        String countSql = "SELECT COUNT(*) FROM audit_log " + where;
        Long totalCount = jdbc.queryForObject(countSql, Long.class, params.toArray());

        // Sort column validation
        String sortCol = ALLOWED_SORT_COLUMNS.contains(query.sort()) ? query.sort() : "timestamp";
        String order = "asc".equalsIgnoreCase(query.order()) ? "ASC" : "DESC";

        String dataSql = "SELECT * FROM audit_log " + where
                + " ORDER BY " + sortCol + " " + order
                + " LIMIT ? OFFSET ?";
        List<Object> dataParams = new ArrayList<>(params);
        dataParams.add(pageSize);
        dataParams.add(offset);

        List<AuditRecord> items = jdbc.query(dataSql, (rs, rowNum) -> mapRecord(rs), dataParams.toArray());
        return new AuditPage(items, totalCount != null ? totalCount : 0);
    }

    @SuppressWarnings("unchecked")
    private AuditRecord mapRecord(ResultSet rs) throws SQLException {
        Map<String, Object> detail = null;
        String detailStr = rs.getString("detail");
        if (detailStr != null) {
            try {
                detail = objectMapper.readValue(detailStr, Map.class);
            } catch (JsonProcessingException e) {
                // leave detail as null if unparseable
            }
        }

        Timestamp ts = rs.getTimestamp("timestamp");
        String categoryStr = rs.getString("category");
        String resultStr = rs.getString("result");

        return new AuditRecord(
                rs.getLong("id"),
                ts != null ? ts.toInstant() : null,
                rs.getString("username"),
                rs.getString("action"),
                categoryStr != null ? AuditCategory.valueOf(categoryStr) : null,
                rs.getString("target"),
                detail,
                resultStr != null ? AuditResult.valueOf(resultStr) : null,
                rs.getString("ip_address"),
                rs.getString("user_agent")
        );
    }
}
