package com.social.media.application.campaign.service;

import com.social.media.application.campaign.dto.*;
import com.social.media.domain.campaign.aggregate.Campaign;
import com.social.media.domain.campaign.repository.CampaignRepository;
import com.social.media.domain.campaign.service.CampaignDomainService;
import com.social.media.domain.campaign.valueobject.CampaignId;
import com.social.media.domain.campaign.valueobject.CampaignInteractionStatus;
import com.social.media.domain.campaign.valueobject.CampaignInteractionType;
import com.social.media.domain.user.valueobject.UserId;
import com.social.media.domain.socialaccount.valueobject.SocialAccountId;
import com.social.media.domain.bot.valueobject.BotId;
import com.social.media.domain.shared.exception.BusinessRuleViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Application service for Campaign operations
 */
@Service
@Transactional
public class CampaignApplicationService {
    
    private final CampaignRepository campaignRepository;
    private final CampaignDomainService campaignDomainService;
    
    public CampaignApplicationService(
            CampaignRepository campaignRepository,
            CampaignDomainService campaignDomainService) {
        this.campaignRepository = campaignRepository;
        this.campaignDomainService = campaignDomainService;
    }
    
    /**
     * Create a new campaign
     */
    public CampaignDTO createCampaign(CreateCampaignDTO dto) {
        Campaign campaign = new Campaign(
            dto.name(),
            dto.description(),
            UserId.of(dto.userId()),
            SocialAccountId.of(dto.accountNetworkId()),
            BotId.of(dto.botId()),
            dto.typeInteration()
        );
        
        // Set optional fields
        if (dto.scriptId() != null) {
            campaign.updateInteractionDetails(
                dto.textInteration(),
                dto.scriptId(),
                dto.listId()
            );
        }
        
        if (dto.dateStart() != null || dto.dateEnd() != null) {
            campaign.updateSchedule(
                dto.dateInteration(),
                dto.hourInteration(),
                dto.dateStart(),
                dto.hourStart(),
                dto.dateEnd(),
                dto.hourEnd()
            );
        }
        
        // Validate business rules
        campaignDomainService.validateCampaignCreation(campaign);
        
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Update an existing campaign
     */
    public CampaignDTO updateCampaign(Long campaignId, UpdateCampaignDTO dto) {
        Campaign campaign = findCampaignById(campaignId);
        
        // Validate business rules
        campaignDomainService.validateCampaignUpdate(campaign, dto.name());
        
        // Update basic info
        campaign.updateBasicInfo(dto.name(), dto.description());
        
        // Update interaction details
        campaign.updateInteractionDetails(
            dto.textInteration(),
            dto.scriptId(),
            dto.listId()
        );
        
        // Update schedule
        campaign.updateSchedule(
            dto.dateInteration(),
            dto.hourInteration(),
            dto.dateStart(),
            dto.hourStart(),
            dto.dateEnd(),
            dto.hourEnd()
        );
        
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Get campaign by ID
     */
    @Transactional(readOnly = true)
    public Optional<CampaignDTO> getCampaignById(Long campaignId) {
        return campaignRepository.findById(CampaignId.of(campaignId))
                .map(this::mapToDTO);
    }
    
    /**
     * Get all campaigns for a user
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByUserId(Long userId) {
        return campaignRepository.findByUserId(UserId.of(userId))
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns by user and status
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByUserIdAndStatus(Long userId, CampaignInteractionStatus status) {
        return campaignRepository.findByUserIdAndStatus(UserId.of(userId), status)
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns by user and interaction type
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByUserIdAndInteractionType(Long userId, CampaignInteractionType interactionType) {
        return campaignRepository.findByUserIdAndInteractionType(UserId.of(userId), interactionType)
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns by bot
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByBotId(Long botId) {
        return campaignRepository.findByBotId(BotId.of(botId))
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns by social account
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByAccountNetworkId(Long accountNetworkId) {
        return campaignRepository.findByAccountNetworkId(SocialAccountId.of(accountNetworkId))
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get active campaigns
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getActiveCampaigns() {
        return campaignRepository.findActiveCampaigns()
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns scheduled for a specific date
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsScheduledForDate(LocalDate date) {
        return campaignRepository.findScheduledForDate(date)
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Get campaigns by date range
     */
    @Transactional(readOnly = true)
    public List<CampaignDTO> getCampaignsByDateRange(Long userId, LocalDate startDate, LocalDate endDate) {
        return campaignRepository.findByUserIdAndDateRange(UserId.of(userId), startDate, endDate)
                .stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Start a campaign
     */
    public CampaignDTO startCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        campaign.start();
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Pause a campaign
     */
    public CampaignDTO pauseCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        campaign.pause();
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Resume a campaign
     */
    public CampaignDTO resumeCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        campaign.resume();
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Complete a campaign
     */
    public CampaignDTO completeCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        campaign.complete();
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Cancel a campaign
     */
    public CampaignDTO cancelCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        campaign.cancel();
        Campaign savedCampaign = campaignRepository.save(campaign);
        return mapToDTO(savedCampaign);
    }
    
    /**
     * Bulk status change
     */
    public List<CampaignDTO> bulkStatusChange(BulkStatusChangeDTO dto, Long userId) {
        List<CampaignId> campaignIds = dto.campaignIds().stream()
                .map(CampaignId::of)
                .collect(Collectors.toList());
        
        // Validate bulk operation
        campaignDomainService.validateBulkStatusChange(campaignIds, dto.newStatus(), UserId.of(userId));
        
        List<Campaign> campaigns = campaignIds.stream()
                .map(id -> campaignRepository.findById(id)
                        .orElseThrow(() -> new BusinessRuleViolationException("Campaign not found: " + id)))
                .collect(Collectors.toList());
        
        // Update status for all campaigns
        campaigns.forEach(campaign -> campaign.updateStatus(dto.newStatus()));
        
        // Save all campaigns
        List<Campaign> savedCampaigns = campaigns.stream()
                .map(campaignRepository::save)
                .collect(Collectors.toList());
        
        return savedCampaigns.stream()
                .map(this::mapToDTO)
                .collect(Collectors.toList());
    }
    
    /**
     * Delete a campaign
     */
    public void deleteCampaign(Long campaignId) {
        Campaign campaign = findCampaignById(campaignId);
        
        // Validate business rules
        campaignDomainService.validateCampaignDeletion(campaign);
        
        campaignRepository.deleteById(CampaignId.of(campaignId));
    }
    
    /**
     * Get campaign statistics for a user
     */
    @Transactional(readOnly = true)
    public CampaignDomainService.CampaignStatistics getCampaignStatistics(Long userId) {
        return campaignDomainService.getCampaignStatistics(UserId.of(userId));
    }
    
    /**
     * Get interaction types
     */
    @Transactional(readOnly = true)
    public CampaignInteractionType[] getInteractionTypes() {
        return CampaignInteractionType.values();
    }
    
    /**
     * Get interaction statuses
     */
    @Transactional(readOnly = true)
    public CampaignInteractionStatus[] getInteractionStatuses() {
        return CampaignInteractionStatus.values();
    }
    
    // Helper methods
    private Campaign findCampaignById(Long campaignId) {
        return campaignRepository.findById(CampaignId.of(campaignId))
                .orElseThrow(() -> new BusinessRuleViolationException("Campaign not found: " + campaignId));
    }
    
    private CampaignDTO mapToDTO(Campaign campaign) {
        return new CampaignDTO(
            campaign.getId().value(),
            campaign.getName(),
            campaign.getDescription(),
            campaign.getUserId().value(),
            campaign.getAccountNetworkId().value(),
            campaign.getBotId().value(),
            campaign.getScriptId(),
            campaign.getListId(),
            campaign.getTypeInteration(),
            campaign.getStatusInteration(),
            campaign.getDateInteration(),
            campaign.getHourInteration(),
            campaign.getTextInteration(),
            campaign.getDateStart(),
            campaign.getHourStart(),
            campaign.getDateEnd(),
            campaign.getHourEnd(),
            campaign.getCreatedAt(),
            campaign.getUpdatedAt(),
            campaign.getVersion()
        );
    }
}
