package de.effigenix.infrastructure.audit;

import de.effigenix.application.usermanagement.AuditEvent;
import de.effigenix.application.usermanagement.AuditLogger;
import de.effigenix.shared.security.ActorId;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;

/**
 * Database-backed implementation of AuditLogger.
 *
 * HACCP/GoBD Compliance:
 * - Logs are written to database for durability
 * - Logs are immutable after creation
 * - Includes IP address and user agent for forensics
 * - Retention: 10 years (gesetzlich)
 *
 * Architecture:
 * - Infrastructure Layer implementation
 * - Adapts Application Layer's AuditLogger port
 * - Uses REQUIRES_NEW transaction to ensure audit logs are committed even if business transaction fails
 */
@Service
@Profile("!no-db")
public class DatabaseAuditLogger implements AuditLogger {

    private static final Logger log = LoggerFactory.getLogger(DatabaseAuditLogger.class);

    private final AuditLogJpaRepository repository;

    public DatabaseAuditLogger(AuditLogJpaRepository repository) {
        this.repository = repository;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(AuditEvent event, String entityId, ActorId performedBy) {
        try {
            AuditLogEntity auditLog = new AuditLogEntity(
                    UUID.randomUUID().toString(),
                    event,
                    entityId,
                    performedBy.value(),
                    null,  // no additional details
                    OffsetDateTime.now(ZoneOffset.UTC),
                    getClientIpAddress(),
                    getUserAgent()
            );

            repository.save(auditLog);
            log.debug("Audit log created: event={}, entityId={}, actor={}", event, entityId, performedBy.value());
        } catch (Exception e) {
            // Never fail business logic due to audit logging errors
            log.error("Failed to create audit log: event={}, entityId={}, actor={}", event, entityId, performedBy.value(), e);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(AuditEvent event, String details) {
        try {
            AuditLogEntity auditLog = new AuditLogEntity(
                    UUID.randomUUID().toString(),
                    event,
                    null,  // no entity ID
                    null,  // no actor (e.g., system event)
                    details,
                    OffsetDateTime.now(ZoneOffset.UTC),
                    getClientIpAddress(),
                    getUserAgent()
            );

            repository.save(auditLog);
            log.debug("Audit log created: event={}, details={}", event, details);
        } catch (Exception e) {
            log.error("Failed to create audit log: event={}, details={}", event, details, e);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(AuditEvent event, String entityId, String details, ActorId performedBy) {
        try {
            AuditLogEntity auditLog = new AuditLogEntity(
                    UUID.randomUUID().toString(),
                    event,
                    entityId,
                    performedBy.value(),
                    details,
                    OffsetDateTime.now(ZoneOffset.UTC),
                    getClientIpAddress(),
                    getUserAgent()
            );

            repository.save(auditLog);
            log.debug("Audit log created: event={}, entityId={}, details={}, actor={}", event, entityId, details, performedBy.value());
        } catch (Exception e) {
            log.error("Failed to create audit log: event={}, entityId={}, details={}, actor={}", event, entityId, details, performedBy.value(), e);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(AuditEvent event, ActorId performedBy) {
        try {
            AuditLogEntity auditLog = new AuditLogEntity(
                    UUID.randomUUID().toString(),
                    event,
                    null,  // no entity ID
                    performedBy.value(),
                    null,  // no additional details
                    OffsetDateTime.now(ZoneOffset.UTC),
                    getClientIpAddress(),
                    getUserAgent()
            );

            repository.save(auditLog);
            log.debug("Audit log created: event={}, actor={}", event, performedBy.value());
        } catch (Exception e) {
            log.error("Failed to create audit log: event={}, actor={}", event, performedBy.value(), e);
        }
    }

    /**
     * Extracts client IP address from HTTP request.
     * Handles X-Forwarded-For header for proxy/load balancer scenarios.
     */
    private String getClientIpAddress() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return "unknown";
            }

            HttpServletRequest request = attributes.getRequest();

            // Check X-Forwarded-For header (proxy/load balancer)
            String xForwardedFor = request.getHeader("X-Forwarded-For");
            if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
                // X-Forwarded-For: client, proxy1, proxy2
                return xForwardedFor.split(",")[0].trim();
            }

            // Fallback to remote address
            return request.getRemoteAddr();
        } catch (Exception e) {
            log.warn("Failed to extract client IP address", e);
            return "unknown";
        }
    }

    /**
     * Extracts User-Agent header from HTTP request.
     */
    private String getUserAgent() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return "unknown";
            }

            HttpServletRequest request = attributes.getRequest();
            String userAgent = request.getHeader("User-Agent");
            return userAgent != null ? userAgent : "unknown";
        } catch (Exception e) {
            log.warn("Failed to extract User-Agent", e);
            return "unknown";
        }
    }
}
