package com.social.media.infrastructure.persistence.repository;

import com.social.media.application.shared.security.AuthenticationService;
import com.social.media.domain.campaign.entity.CampaignExecution;
import com.social.media.domain.campaign.repository.CampaignExecutionRepository;
import com.social.media.domain.campaign.valueobject.*;
import com.social.media.infrastructure.persistence.entity.CampaignExecutionEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Implementation of CampaignExecutionRepository using Spring Data JPA - REFACTORED VERSION
 * All operations now require userId and companyId for security isolation
 */
@Repository
@RequiredArgsConstructor
public class CampaignExecutionRepositoryImpl implements CampaignExecutionRepository {
    
    private final CampaignExecutionJpaRepository jpaRepository;
    private final AuthenticationService authenticationService;
    
    private Long getCurrentUserId() {
        return authenticationService.getAuthenticatedUserInfo().getUserIdAsLong();
    }
    
    private Long getCurrentCompanyId() {
        return authenticationService.getAuthenticatedUserInfo().companyIdAsLong();
    }
    
    @Override
    public CampaignExecution save(CampaignExecution execution) {
        CampaignExecutionEntity entity = mapToEntity(execution);
        CampaignExecutionEntity savedEntity = jpaRepository.save(entity);
        return mapToDomain(savedEntity);
    }
    
    @Override
    public Optional<CampaignExecution> findById(CampaignExecutionId executionId) {
        return jpaRepository.findByIdAndCompanyIdAndUserId(
                executionId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId())
                .map(this::mapToDomain);
    }
    
    @Override
    public void deleteById(CampaignExecutionId executionId) {
        // Validate access before deletion
        if (jpaRepository.findByIdAndCompanyIdAndUserId(
                executionId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId()).isPresent()) {
            jpaRepository.deleteById(executionId.getValue());
        }
    }
    
    @Override
    public boolean existsById(CampaignExecutionId executionId) {
        return jpaRepository.findByIdAndCompanyIdAndUserId(
                executionId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId()).isPresent();
    }
    
    @Override
    public List<CampaignExecution> findByCampaignId(AutomationCampaignId campaignId) {
        return jpaRepository.findByCampaignIdAndCompanyIdAndUserId(
                campaignId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId())
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByCampaignIdAndStatus(AutomationCampaignId campaignId, ExecutionStatus status) {
        CampaignExecutionEntity.ExecutionStatusEntity entityStatus = mapToEntityStatus(status);
        return jpaRepository.findByCampaignIdAndStatusAndCompanyIdAndUserId(
                campaignId.getValue(), 
                entityStatus, 
                getCurrentCompanyId(), 
                getCurrentUserId())
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByCampaignIdAndActionType(AutomationCampaignId campaignId, ActionType actionType) {
        return jpaRepository.findByCampaignIdAndActionTypeAndCompanyIdAndUserId(
                campaignId.getValue(), 
                actionType.name(), 
                getCurrentCompanyId(), 
                getCurrentUserId())
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByStatus(ExecutionStatus status) {
        CampaignExecutionEntity.ExecutionStatusEntity entityStatus = mapToEntityStatus(status);
        return jpaRepository.findByStatusAndCompanyIdAndUserId(
                entityStatus, 
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(0, 1000))
                .getContent()
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findPendingExecutions() {
        return findByStatus(ExecutionStatus.PENDING);
    }
    
    @Override
    public List<CampaignExecution> findFailedExecutions() {
        return findByStatus(ExecutionStatus.FAILED);
    }
    
    @Override
    public List<CampaignExecution> findRetryableExecutions() {
        return jpaRepository.findExecutionsForRetryByCompanyIdAndUserId(
                CampaignExecutionEntity.ExecutionStatusEntity.FAILED, 
                getCurrentCompanyId(), 
                getCurrentUserId())
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByExecutionTimeBetween(LocalDateTime start, LocalDateTime end) {
        return jpaRepository.findByDateRangeAndCompanyIdAndUserId(
                start, 
                end, 
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(0, 1000))
                .getContent()
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByCompletionTimeBetween(LocalDateTime start, LocalDateTime end) {
        return findByExecutionTimeBetween(start, end)
                .stream()
                .filter(execution -> execution.getStatus() == ExecutionStatus.COMPLETED)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findExecutionsAfter(LocalDateTime dateTime) {
        return jpaRepository.findByDateRangeAndCompanyIdAndUserId(
                dateTime, 
                LocalDateTime.now().plusYears(1), // Far future date
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(0, 1000))
                .getContent()
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByTargetUsername(String targetUsername) {
        return jpaRepository.findRecentActionsByTargetAndCompanyIdAndUserId(
                targetUsername, 
                "", // Empty action type to get all actions
                LocalDateTime.now().minusYears(1), // Far past date
                getCurrentCompanyId(), 
                getCurrentUserId())
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findBySocialAccountId(Long socialAccountId) {
        // This would require a join with campaign table to get social account
        // For now, returning empty list as temporary implementation
        return List.of();
    }
    
    @Override
    public List<CampaignExecution> findRateLimitedExecutions() {
        // Implementation would depend on how rate limiting is tracked
        // For now, returning empty list as temporary implementation
        return List.of();
    }
    
    @Override
    public List<CampaignExecution> findExecutionsWithRateLimitResetBefore(LocalDateTime dateTime) {
        // Implementation would depend on how rate limiting is tracked
        // For now, returning empty list as temporary implementation
        return List.of();
    }
    
    @Override
    public List<CampaignExecution> findByErrorCode(String errorCode) {
        // This would require adding an error code field or searching error messages
        // For now, returning empty list as temporary implementation
        return List.of();
    }
    
    @Override
    public List<CampaignExecution> findExecutionsWithErrors() {
        // This would require filtering by error message not null/empty
        // For now, returning failed executions as approximation
        return findFailedExecutions();
    }
    
    @Override
    public List<CampaignExecution> findByCampaignIdWithPagination(AutomationCampaignId campaignId, int page, int size) {
        return jpaRepository.findByCampaignIdAndCompanyIdAndUserId(
                campaignId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(page, size))
                .getContent()
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CampaignExecution> findByStatusWithPagination(ExecutionStatus status, int page, int size) {
        CampaignExecutionEntity.ExecutionStatusEntity entityStatus = mapToEntityStatus(status);
        return jpaRepository.findByStatusAndCompanyIdAndUserId(
                entityStatus, 
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(page, size))
                .getContent()
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public long countByCampaignId(AutomationCampaignId campaignId) {
        return jpaRepository.countByCampaignIdAndCompanyIdAndUserId(
                campaignId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId());
    }
    
    @Override
    public long countByCampaignIdAndStatus(AutomationCampaignId campaignId, ExecutionStatus status) {
        CampaignExecutionEntity.ExecutionStatusEntity entityStatus = mapToEntityStatus(status);
        return jpaRepository.countByCampaignIdAndStatusAndCompanyIdAndUserId(
                campaignId.getValue(), 
                entityStatus, 
                getCurrentCompanyId(), 
                getCurrentUserId());
    }
    
    @Override
    public long countByActionType(ActionType actionType) {
        // This would require a specific query for action type counting
        // For now, getting all executions and filtering (not efficient but functional)
        return jpaRepository.findByCompanyIdAndUserId(
                getCurrentCompanyId(), 
                getCurrentUserId(), 
                PageRequest.of(0, Integer.MAX_VALUE))
                .stream()
                .filter(entity -> actionType.name().equals(entity.getActionType()))
                .count();
    }
    
    @Override
    public long countSuccessfulExecutions(AutomationCampaignId campaignId) {
        return countByCampaignIdAndStatus(campaignId, ExecutionStatus.COMPLETED);
    }
    
    @Override
    public long countFailedExecutions(AutomationCampaignId campaignId) {
        return countByCampaignIdAndStatus(campaignId, ExecutionStatus.FAILED);
    }
    
    @Override
    public List<CampaignExecution> saveAll(List<CampaignExecution> executions) {
        List<CampaignExecutionEntity> entities = executions.stream()
                .map(this::mapToEntity)
                .collect(Collectors.toList());
        return jpaRepository.saveAll(entities)
                .stream()
                .map(this::mapToDomain)
                .collect(Collectors.toList());
    }
    
    @Override
    public void deleteAllByCampaignId(AutomationCampaignId campaignId) {
        List<CampaignExecutionEntity> entities = jpaRepository.findByCampaignIdAndCompanyIdAndUserId(
                campaignId.getValue(), 
                getCurrentCompanyId(), 
                getCurrentUserId());
        jpaRepository.deleteAll(entities);
    }
    
    @Override
    public List<CampaignExecution> findExecutionsForMetricsCalculation(AutomationCampaignId campaignId) {
        return findByCampaignId(campaignId);
    }
    
    @Override
    public List<CampaignExecution> findRecentExecutions(LocalDateTime since) {
        return findExecutionsAfter(since);
    }
    
    // Mapping methods
    private CampaignExecution mapToDomain(CampaignExecutionEntity entity) {
        return CampaignExecution.builder()
                .id(entity.getId())
                .executionCode(entity.getExecutionCode())
                .campaignId(entity.getCampaignId())
                .actionType(ActionType.valueOf(entity.getActionType()))
                .status(mapToDomainStatus(entity.getStatus()))
                .targetUserId(entity.getTargetUserId())
                .targetPostId(entity.getTargetPostId())
                .targetCommentId(entity.getTargetCommentId())
                .actionParams(entity.getActionParams())
                .resultData(entity.getResultData())
                .plannedExecutionDate(entity.getPlannedExecutionDate())
                .actualExecutionDate(entity.getActualExecutionDate())
                .errorMessage(entity.getErrorMessage())
                .retryCount(entity.getRetryCount())
                .maxRetries(entity.getMaxRetries())
                .executionDurationMs(entity.getExecutionDurationMs())
                .createdAt(entity.getCreatedAt())
                .build();
    }
    
    private CampaignExecutionEntity mapToEntity(CampaignExecution domain) {
        return CampaignExecutionEntity.builder()
                .id(domain.getId())
                .executionCode(domain.getExecutionCode())
                .campaignId(domain.getCampaignId())
                .actionType(domain.getActionType().name())
                .status(mapToEntityStatus(domain.getStatus()))
                .targetUserId(domain.getTargetUserId())
                .targetPostId(domain.getTargetPostId())
                .targetCommentId(domain.getTargetCommentId())
                .actionParams(domain.getActionParams())
                .resultData(domain.getResultData())
                .plannedExecutionDate(domain.getPlannedExecutionDate())
                .actualExecutionDate(domain.getActualExecutionDate())
                .errorMessage(domain.getErrorMessage())
                .retryCount(domain.getRetryCount())
                .maxRetries(domain.getMaxRetries())
                .executionDurationMs(domain.getExecutionDurationMs())
                .createdAt(domain.getCreatedAt())
                .build();
    }
    
    private ExecutionStatus mapToDomainStatus(CampaignExecutionEntity.ExecutionStatusEntity entityStatus) {
        return switch (entityStatus) {
            case PENDING -> ExecutionStatus.PENDING;
            case IN_PROGRESS -> ExecutionStatus.RUNNING;
            case COMPLETED -> ExecutionStatus.COMPLETED;
            case FAILED -> ExecutionStatus.FAILED;
            case CANCELLED -> ExecutionStatus.CANCELLED;
            case SKIPPED -> ExecutionStatus.CANCELLED; // Map SKIPPED to CANCELLED for now
        };
    }
    
    private CampaignExecutionEntity.ExecutionStatusEntity mapToEntityStatus(ExecutionStatus domainStatus) {
        return switch (domainStatus) {
            case PENDING -> CampaignExecutionEntity.ExecutionStatusEntity.PENDING;
            case RUNNING -> CampaignExecutionEntity.ExecutionStatusEntity.IN_PROGRESS;
            case COMPLETED -> CampaignExecutionEntity.ExecutionStatusEntity.COMPLETED;
            case FAILED -> CampaignExecutionEntity.ExecutionStatusEntity.FAILED;
            case CANCELLED -> CampaignExecutionEntity.ExecutionStatusEntity.CANCELLED;
        };
    }
}
