package com.saas.admin.service.impl;

import com.saas.admin.dto.response.AuditLogResponse;
import com.saas.admin.repository.AuditLogRepository;
import com.saas.admin.service.AuditLogService;
import com.saas.shared.audit.entity.AuditLog;
import com.saas.shared.dto.common.PageResponse;
import com.saas.shared.dto.mapper.AuditLogMapper;
import com.saas.shared.exception.BusinessException;
import com.saas.shared.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Audit Log Service Implementation
 * 
 * Implements business logic for audit log queries and statistics.
 * Uses Spring Data JPA Specifications for dynamic, type-safe filtering.
 * 
 * Design Patterns:
 * - Service Pattern: Encapsulates business logic
 * - Specification Pattern: Dynamic query construction
 * - Repository Pattern: Data access abstraction
 * - DTO Pattern: Separation of API concerns from entities
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class AuditLogServiceImpl implements AuditLogService {

    private final AuditLogRepository auditLogRepository;
    private final AuditLogMapper auditLogMapper;

    /**
     * Get paginated audit logs with dynamic filtering using Specifications
     * 
     * This method constructs a Specification by combining multiple optional filters
     * in a type-safe manner, avoiding string-based queries.
     */
    @Override
    public PageResponse<AuditLogResponse> getAuditLogs(
            int page, int size, String action, String entityType,
            Long userId, String tenantId, LocalDate startDate, LocalDate endDate) {

        log.debug("Querying audit logs with filters - action: {}, entityType: {}, userId: {}, tenantId: {}", 
                  action, entityType, userId, tenantId);

        // Build dynamic Specification based on provided filters
        Specification<AuditLog> spec = buildAuditLogSpecification(
            action, entityType, userId, tenantId, startDate, endDate
        );

        // Create pageable with sorting by createdAt descending (most recent first)
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());

        // Execute query with specification
        Page<AuditLog> auditLogs = auditLogRepository.findAll(spec, pageable);

        // Map to response DTOs
        List<AuditLogResponse> content = auditLogs.getContent().stream()
            .map(auditLogMapper::toResponse)
            .collect(Collectors.toList());

        return PageResponse.<AuditLogResponse>builder()
            .content(content)
            .page(page)
            .size(size)
            .totalElements(auditLogs.getTotalElements())
            .totalPages(auditLogs.getTotalPages())
            .hasNext(auditLogs.hasNext())
            .hasPrevious(auditLogs.hasPrevious())
            .build();
    }

    /**
     * Get single audit log by ID
     */
    @Override
    public AuditLogResponse getAuditLogById(Long id) {
        log.debug("Fetching audit log: {}", id);

        AuditLog auditLog = auditLogRepository.findById(id)
            .orElseThrow(() -> new BusinessException(ErrorCode.SUBSCRIPTION_PLAN_NOT_FOUND,
                "Audit log not found with ID: " + id));

        return auditLogMapper.toResponse(auditLog);
    }

    /**
     * Get paginated audit logs for a specific tenant
     */
    @Override
    public PageResponse<AuditLogResponse> getTenantAuditLogs(String tenantId, int page, int size) {
        log.debug("Fetching audit logs for tenant: {}", tenantId);

        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        Page<AuditLog> auditLogs = auditLogRepository.findByTenantId(tenantId, pageable);

        List<AuditLogResponse> content = auditLogs.getContent().stream()
            .map(auditLogMapper::toResponse)
            .collect(Collectors.toList());

        return PageResponse.<AuditLogResponse>builder()
            .content(content)
            .page(page)
            .size(size)
            .totalElements(auditLogs.getTotalElements())
            .totalPages(auditLogs.getTotalPages())
            .hasNext(auditLogs.hasNext())
            .hasPrevious(auditLogs.hasPrevious())
            .build();
    }

    /**
     * Get paginated audit logs for a specific user
     */
    @Override
    public PageResponse<AuditLogResponse> getUserAuditLogs(Long userId, int page, int size) {
        log.debug("Fetching audit logs for user: {}", userId);

        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        Page<AuditLog> auditLogs = auditLogRepository.findByUserId(userId, pageable);

        List<AuditLogResponse> content = auditLogs.getContent().stream()
            .map(auditLogMapper::toResponse)
            .collect(Collectors.toList());

        return PageResponse.<AuditLogResponse>builder()
            .content(content)
            .page(page)
            .size(size)
            .totalElements(auditLogs.getTotalElements())
            .totalPages(auditLogs.getTotalPages())
            .hasNext(auditLogs.hasNext())
            .hasPrevious(auditLogs.hasPrevious())
            .build();
    }

    /**
     * Get audit log statistics and aggregations
     * 
     * Returns various aggregated metrics for dashboard/analytics purposes.
     * Stats are computed from the database using GROUP BY queries for efficiency.
     */
    @Override
    public Map<String, Object> getAuditLogStatistics() {
        log.debug("Computing audit log statistics");

        Map<String, Object> stats = new HashMap<>();

        // Count by action
        Map<String, Long> actionCounts = auditLogRepository.countByActionGrouped().stream()
            .collect(Collectors.toMap(
                row -> (String) row[0],
                row -> ((Number) row[1]).longValue()
            ));
        stats.put("actionCounts", actionCounts);

        // Count by entity type
        Map<String, Long> entityTypeCounts = auditLogRepository.countByEntityTypeGrouped().stream()
            .collect(Collectors.toMap(
                row -> (String) row[0],
                row -> ((Number) row[1]).longValue()
            ));
        stats.put("entityTypeCounts", entityTypeCounts);

        // Count by tenant
        Map<String, Long> tenantCounts = auditLogRepository.countByTenantGrouped().stream()
            .collect(Collectors.toMap(
                row -> (String) row[0],
                row -> ((Number) row[1]).longValue()
            ));
        stats.put("tenantCounts", tenantCounts);

        // Count by user (top 10)
        Map<Long, Long> userCounts = auditLogRepository.countByUserGrouped().stream()
            .collect(Collectors.toMap(
                row -> ((Number) row[0]).longValue(),
                row -> ((Number) row[1]).longValue()
            ));
        stats.put("userCounts", userCounts);

        // Recent activity (last 24 hours)
        long logsLast24h = auditLogRepository.countByCreatedAtAfter(
            LocalDateTime.now().minusHours(24)
        );
        stats.put("logsLast24h", logsLast24h);

        // Total logs
        long totalLogs = auditLogRepository.count();
        stats.put("totalLogs", totalLogs);

        log.debug("Computed audit stats - actions: {}, entities: {}, tenants: {}, last24h: {}", 
                  actionCounts.size(), entityTypeCounts.size(), tenantCounts.size(), logsLast24h);

        return stats;
    }

    /**
     * Build dynamic Specification for filtering audit logs
     * 
     * Uses Specification pattern for type-safe, composable query construction.
     * Each filter is optional and combined with AND logic.
     */
    private Specification<AuditLog> buildAuditLogSpecification(
            String action, String entityType, Long userId, String tenantId,
            LocalDate startDate, LocalDate endDate) {

        Specification<AuditLog> spec = null;
        
        if (action != null && !action.isEmpty()) {
            spec = (root, query, cb) -> cb.equal(root.get("action"), action);
        }
        if (entityType != null && !entityType.isEmpty()) {
            Specification<AuditLog> entityTypeSpec = (root, query, cb) -> cb.equal(root.get("entityType"), entityType);
            spec = (spec == null) ? entityTypeSpec : spec.and(entityTypeSpec);
        }
        if (userId != null) {
            Specification<AuditLog> userIdSpec = (root, query, cb) -> cb.equal(root.get("userId"), userId);
            spec = (spec == null) ? userIdSpec : spec.and(userIdSpec);
        }
        if (tenantId != null && !tenantId.isEmpty()) {
            Specification<AuditLog> tenantIdSpec = (root, query, cb) -> cb.equal(root.get("tenantId"), tenantId);
            spec = (spec == null) ? tenantIdSpec : spec.and(tenantIdSpec);
        }
        if (startDate != null && endDate != null) {
            Specification<AuditLog> dateSpec = (root, query, cb) -> cb.between(root.get("createdAt"),
                startDate.atStartOfDay(),
                endDate.atTime(23, 59, 59));
            spec = (spec == null) ? dateSpec : spec.and(dateSpec);
        }
        
        return spec;
    }
}
