package kz.konturai.parser.service;

import kz.konturai.parser.dto.StatusHistoryEntry;
import kz.konturai.parser.dto.TargetingAIAnalysisResult;
import kz.konturai.parser.dto.TargetingCampaignRequest;
import kz.konturai.parser.dto.TargetingPerformanceDto;
import kz.konturai.parser.dto.targeting.AudienceProfileDto;
import kz.konturai.parser.dto.targeting.TargetingRecommendationDto;
import kz.konturai.parser.enums.CampaignStatusMessage;
import kz.konturai.parser.exception.TargetingCampaignNotFoundException;
import kz.konturai.parser.exception.ValidationException;
import kz.konturai.parser.model.*;
import kz.konturai.parser.repository.MarketingAnalysisV3Repository;
import kz.konturai.parser.repository.MarketingStrategyRepository;
import kz.konturai.parser.repository.TargetingCampaignRepository;
import kz.konturai.parser.dto.targeting.TargetingOrchestrationDto;
import kz.konturai.parser.enums.CampaignObjective;
import kz.konturai.parser.util.RangeParser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class TargetingCampaignService {

    private final TargetingCampaignRepository repository;
    private final MarketingAnalysisV3Repository analysisRepository;
    private final AiTargetologistService aiService;
    private final AiTargetingSystemService aiTargetingSystemService;
    private final ABTestingService abTestingService;
    private final SocialMediaCredentialsService credentialsService;
    private final TargetingInsightService insightService;
    private final MarketingStrategyRepository strategyRepository;
    private final CampaignPredictionService predictionService;

    // Self-reference through Spring proxy so @Async works (avoids self-invocation bypass)
    @Autowired
    private ApplicationContext applicationContext;

    public TargetingCampaign createCampaign(TargetingCampaignRequest req, String userId) {
        if (req.getStrategyId() == null || req.getStrategyId().isBlank()) {
            throw new IllegalArgumentException("strategyId is required");
        }

        MarketingStrategy strategy = strategyRepository.findById(req.getStrategyId())
                .orElseThrow(() -> new IllegalArgumentException("Strategy not found: " + req.getStrategyId()));
        if (strategy.getAnalysisId() == null || strategy.getAnalysisId().isBlank()) {
            throw new IllegalArgumentException("Strategy does not contain analysisId: " + req.getStrategyId());
        }

        MarketingAnalysisV3Document analysis = analysisRepository.findById(strategy.getAnalysisId())
                .orElseThrow(() -> new IllegalArgumentException("Analysis not found: " + strategy.getAnalysisId()));

        if (req.getTotalBudgetKzt() == null || req.getTotalBudgetKzt() <= 0) {
            throw new ValidationException("totalBudgetKzt", req.getTotalBudgetKzt(), "Бюджет должен быть больше 0");
        }
        if (req.getTotalBudgetKzt() > 500_000_000) {
            throw new ValidationException("totalBudgetKzt", req.getTotalBudgetKzt(), "Бюджет не может превышать 500,000,000 KZT");
        }

        TargetingRecommendationDto recommendation =
                aiTargetingSystemService.generateLinkedRecommendation(strategy, analysis);

        // Вычисляем дату окончания из длительности стратегии
        int durationDays = strategy.getDurationWeeks() != null
                ? strategy.getDurationWeeks() * 7
                : 30;
        LocalDateTime startDate = req.getStartDate() != null ? req.getStartDate() : LocalDateTime.now();
        LocalDateTime endDate = req.getEndDate() != null ? req.getEndDate() : startDate.plusDays(durationDays);

        // Если дневной бюджет не передан — вычисляем автоматически
        if (req.getDailyBudgetKzt() == null || req.getDailyBudgetKzt() <= 0) {
            req.setDailyBudgetKzt(Math.round(req.getTotalBudgetKzt() / durationDays * 100.0) / 100.0);
        }

        TargetingAudienceProfile audienceProfile =
                buildAudienceProfile(recommendation, req.getAudienceOverride());

        // Заполняем estimatedReach из строки
        if (recommendation != null && recommendation.getBudgetRecommendation() != null) {
            String reach = recommendation.getBudgetRecommendation().getEstimatedDailyReach();
            audienceProfile.setEstimatedReachMin(RangeParser.extractMin(reach));
            audienceProfile.setEstimatedReachMax(RangeParser.extractMax(reach));
        }

        // Перезаписываем бюджетные поля AI-рекомендации реальными значениями пользователя,
        // чтобы AI не показывал фантастические миллиарды
        if (recommendation != null) {
            kz.konturai.parser.dto.targeting.TargetingBudgetDto realBudget =
                kz.konturai.parser.dto.targeting.TargetingBudgetDto.builder()
                    .dailyBudget(String.format("%.0f KZT", req.getDailyBudgetKzt()))
                    .monthlyBudget(String.format("%.0f KZT", req.getTotalBudgetKzt()))
                    .bidStrategy(recommendation.getBudgetRecommendation() != null
                        ? recommendation.getBudgetRecommendation().getBidStrategy()
                        : "LOWEST_COST_WITHOUT_CAP")
                    .rationale(recommendation.getBudgetRecommendation() != null
                        ? recommendation.getBudgetRecommendation().getRationale()
                        : null)
                    .estimatedCpc(recommendation.getBudgetRecommendation() != null
                        ? recommendation.getBudgetRecommendation().getEstimatedCpc()
                        : null)
                    .estimatedCpl(recommendation.getBudgetRecommendation() != null
                        ? recommendation.getBudgetRecommendation().getEstimatedCpl()
                        : null)
                    .estimatedDailyReach(recommendation.getBudgetRecommendation() != null
                        ? recommendation.getBudgetRecommendation().getEstimatedDailyReach()
                        : null)
                    .build();
            recommendation.setBudgetRecommendation(realBudget);
        }

        TargetingAIAnalysisResult aiAnalysisResult =
                buildAiAnalysisResult(strategy, analysis, recommendation);

        BudgetConfig budget = BudgetConfig.builder()
                .totalBudget(req.getTotalBudgetKzt())
                .dailyBudget(req.getDailyBudgetKzt())
                .currency("KZT")
                .startDate(startDate)
                .endDate(endDate)
                .build();

        TargetingCampaign campaign = TargetingCampaign.builder()
                .userId(userId)
                .analysisId(strategy.getAnalysisId())
                .strategyId(req.getStrategyId())
                .name(resolveCampaignName(req, strategy, recommendation))
                .objective(resolveObjective(req, recommendation))
                .status("draft")
                .budget(budget)
                .audience(audienceProfile)
                .aiRecommendations(aiAnalysisResult)
                .performanceMetrics(PerformanceMetrics.empty())
                .durationDays(durationDays)
                .createdAt(LocalDateTime.now())
                .updatedAt(LocalDateTime.now())
                .statusHistory(new ArrayList<>())
                .insights(new ArrayList<>())
                .externalIds(new HashMap<>())
                .build();

        // Map platforms
        campaign.setPlatforms(resolvePlatforms(req, recommendation));

        addStatusHistory(campaign, "CREATED", CampaignStatusMessage.CREATED.getMessage());
        TargetingCampaign saved = repository.save(campaign);

        // Process Async — вызываем через Spring proxy чтобы @Async работал корректно
        applicationContext.getBean(TargetingCampaignService.class).processAsync(saved.getId(), req, userId);

        return saved;
    }

    @Async("targetingExecutor")
    public void processAsync(String campaignId, TargetingCampaignRequest req, String userId) {
        log.info("[Targeting Pipeline] Starting async Antigravity processing for campaign {}", campaignId);
        TargetingCampaign campaign = repository.findById(campaignId).orElseThrow(() -> 
                new TargetingCampaignNotFoundException("Campaign not found async: " + campaignId));

        try {
            MarketingAnalysisV3Document analysis = analysisRepository.findById(campaign.getAnalysisId()).orElse(null);
            if (analysis == null) {
                failCampaign(campaign, "Analysis Document not found: " + campaign.getAnalysisId());
                return;
            }

            MarketingStrategy strategy = strategyRepository.findById(campaign.getStrategyId()).orElse(null);
            if (strategy == null) {
                failCampaign(campaign, "Marketing Strategy containing media assets not found.");
                return;
            }

            List<SocialMediaCredentials> credentials = credentialsService.getUserCredentials(userId);

            addStatusHistory(campaign, "ORCHESTRATING", CampaignStatusMessage.ORCHESTRATING.getMessage());
            repository.save(campaign);

            // 1. Run The Master AI Orchestrator Pipeline
            TargetingOrchestrationDto payload = aiService.orchestrateCampaign(campaign, analysis, strategy, credentials);

            // 2. Map Payload to Local Database Models
            addStatusHistory(campaign, "MAPPING", CampaignStatusMessage.MAPPING.getMessage());

            List<TargetingOrchestrationDto.AdSetDto> allAdSets = new ArrayList<>();
            for (TargetingOrchestrationDto.CampaignDto apiCamp : payload.getCampaigns()) {
                if (apiCamp.getAdsets() != null) allAdSets.addAll(apiCamp.getAdsets());
            }

            List<TargetingAdSet> savedAdSets = new ArrayList<>();
            double totalBudget = campaign.getBudget() != null ? campaign.getBudget().getTotalBudget() : 0.0;
            List<Double> budgetSlices = distributeBudgetSlices(totalBudget, allAdSets.size());

            for (int i = 0; i < allAdSets.size(); i++) {
                TargetingOrchestrationDto.AdSetDto apiAdSet = allAdSets.get(i);
                double adSetBudget = budgetSlices.get(i);

                List<TargetingAd> ads = new ArrayList<>();
                if (apiAdSet.getAds() != null) {
                    boolean hasAbTest = campaign.getAbTestConfig() != null;
                    for (int j = 0; j < apiAdSet.getAds().size(); j++) {
                        TargetingOrchestrationDto.AdDto apiAd = apiAdSet.getAds().get(j);
                        String mediaUrl = apiAd.getCreativePayload().getAssetId();
                        String destUrl = apiAd.getCreativePayload().getDestinationUrl();

                        TargetingAd ad = TargetingAd.builder()
                                .adId(java.util.UUID.randomUUID().toString())
                                .name(apiAd.getAdName())
                                .adFormat(resolveAdFormat(mediaUrl, apiAdSet.getAds().size()))
                                .headline(apiAd.getCreativePayload().getHeadlineId())
                                .primaryText(apiAd.getCreativePayload().getBodyTextId())
                                .callToAction(apiAd.getCreativePayload().getCallToAction())
                                .mediaUrl(mediaUrl)
                                .destinationUrl(destUrl != null && !destUrl.isBlank() ? destUrl : "#")
                                .needsDestinationUrl(destUrl == null || destUrl.isBlank())
                                .abVariant(hasAbTest ? (j == 0 ? "A" : "B") : null)
                                .performanceScore(null)
                                .build();
                        ads.add(ad);
                    }
                }

                TargetingAdSet adSet = TargetingAdSet.builder()
                        .adSetId(java.util.UUID.randomUUID().toString())
                        .name(apiAdSet.getAdsetName())
                        .platform(resolvePlatformFromName(apiAdSet.getAdsetName(), campaign))
                        .budget(adSetBudget)
                        .bidStrategy(apiAdSet.getBidStrategy())
                        .optimizationGoal(apiAdSet.getOptimizationGoal())
                        .placements(resolvePlacements(apiAdSet.getAdsetName(), campaign))
                        .ads(ads)
                        .status("DRAFT")
                        .build();

                savedAdSets.add(adSet);
            }

            campaign.setAdSets(savedAdSets);
            repository.save(campaign);

            // 3. External Platform Publishing
            addStatusHistory(campaign, "PUBLISHING", CampaignStatusMessage.PUBLISHING.getMessage());
            repository.save(campaign);
            publishToPlatforms(campaign, userId);

            // 4. Finalize
            if ("failed".equals(campaign.getStatus())) {
                addStatusHistory(campaign, "FAILED", CampaignStatusMessage.FAILED.getMessage());
            } else {
                campaign.setStatus("active");
                addStatusHistory(campaign, "ACTIVE", CampaignStatusMessage.ACTIVE.getMessage());
                // Запускаем AI-предсказание асинхронно
                predictionService.generateAsync(campaign);
            }

            campaign.setUpdatedAt(LocalDateTime.now());
            campaign.setCompletedAt(LocalDateTime.now());
            repository.save(campaign);

        } catch (Exception e) {
            log.error("[Targeting Pipeline] Error during async targeting pipeline", e);
            failCampaign(campaign, CampaignStatusMessage.FAILED.getMessage() + " Детали: " + e.getMessage());
        }
    }

    private void publishToPlatforms(TargetingCampaign campaign, String userId) {
//        boolean allSuccess = true;
//        
//        if (campaign.getPlatforms() != null) {
//            // FB / IG
//            if (campaign.getPlatforms().contains(kz.konturai.parser.enums.TargetingPlatform.FACEBOOK) || 
//                campaign.getPlatforms().contains(kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM)) {
//                
//                String fbToken = credentialsService.getCredentials(userId, "FACEBOOK");
//                if (fbToken != null) {
//                    try {
//                        SocialMediaCredentials creds = credentialsService.getUserCredentials(userId).stream()
//                                .filter(c -> "facebook".equalsIgnoreCase(c.getPlatform()))
//                                .findFirst().orElse(null);
//                        String adAccountId = (creds != null && creds.getAdAccountId() != null) 
//                                ? creds.getAdAccountId() : "placeholder_ad_account";
//                                
//                        String fbCampId = facebookAdsService.createCampaign(fbToken, adAccountId, campaign);
//                        if (fbCampId != null) {
//                            campaign.getExternalIds().put("FACEBOOK", fbCampId);
//                            log.info("Created FB Campaign {}", fbCampId);
//                        }
//                    } catch (Exception e) {
//                        log.error("Failed to create FB campaign", e);
//                        allSuccess = false;
//                    }
//                } else {
//                    log.warn("Missing FB credentials for user {}", userId);
//                    allSuccess = false;
//                }
//            }
//
//            // TikTok
//            if (campaign.getPlatforms().contains(kz.konturai.parser.enums.TargetingPlatform.TIKTOK)) {
//                String ttToken = credentialsService.getCredentials(userId, "TIKTOK");
//                if (ttToken != null) {
//                    try {
//                        SocialMediaCredentials creds = credentialsService.getUserCredentials(userId).stream()
//                                .filter(c -> "tiktok".equalsIgnoreCase(c.getPlatform()))
//                                .findFirst().orElse(null);
//                        String ttAdvId = (creds != null && creds.getAdAccountId() != null) 
//                                ? creds.getAdAccountId() : "placeholder_tt_adv";
//                                
//                        String ttCampId = tikTokAdsService.createCampaign(ttToken, ttAdvId, campaign);
//                        if (ttCampId != null) {
//                            campaign.getExternalIds().put("TIKTOK", ttCampId);
//                            log.info("Created TT Campaign {}", ttCampId);
//                        }
//                    } catch (Exception e) {
//                        log.error("Failed to create TT campaign", e);
//                        allSuccess = false;
//                    }
//                } else {
//                    log.warn("Missing TT credentials for user {}", userId);
//                    allSuccess = false;
//                }
//            }
//        }
//        
//        if (!allSuccess) {
//            campaign.setStatus("failed");
//        }
    }

    public TargetingCampaign getCampaignById(String id, String userId) {
        TargetingCampaign campaign = repository.findById(id)
                .orElseThrow(() -> new TargetingCampaignNotFoundException("Campaign not found: " + id));
        if (!campaign.getUserId().equals(userId)) {
            throw new TargetingCampaignNotFoundException("Not authorized for this campaign");
        }
        return campaign;
    }

    public Page<TargetingCampaign> getUserCampaigns(String userId, int page, int size) {
        return repository.findByUserIdOrderByCreatedAtDesc(userId, PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")));
    }

    public TargetingCampaign pauseCampaign(String campaignId, String userId) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        campaign.setStatus("paused");
        
//        Map<String, String> ids = campaign.getExternalIds();
//        if (ids != null) {
//            if (ids.containsKey("FACEBOOK")) {
//                facebookAdsService.pauseCampaign(credentialsService.getCredentials(userId, "FACEBOOK"), ids.get("FACEBOOK"));
//            }
//            // Add TikTok pause when needed
//        }
        
        addStatusHistory(campaign, "PAUSED", CampaignStatusMessage.PAUSED.getMessage());
        return repository.save(campaign);
    }

    public TargetingCampaign resumeCampaign(String campaignId, String userId) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        campaign.setStatus("active");
        
//        Map<String, String> ids = campaign.getExternalIds();
//        if (ids != null) {
//            if (ids.containsKey("FACEBOOK")) {
//                facebookAdsService.resumeCampaign(credentialsService.getCredentials(userId, "FACEBOOK"), ids.get("FACEBOOK"));
//            }
//            // Add TikTok resume when needed
//        }
        
        addStatusHistory(campaign, "RESUMED", "Кампания возобновлена.");
        return repository.save(campaign);
    }

    public TargetingPerformanceDto getCampaignInsights(String campaignId, String userId, String datePreset) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        // Simple aggregate for the controller
        TargetingPerformanceDto dto = new TargetingPerformanceDto();
        dto.setTotalSpend(campaign.getPerformanceMetrics() != null ? campaign.getPerformanceMetrics().getSpend() : 0.0);
        return dto;
    }

    @Scheduled(cron = "0 0 */6 * * *") // Every 6 hours
    public void syncInsightsScheduler() {
        log.info("Running scheduled insights sync");
        List<TargetingCampaign> activeCampaigns = repository.findAllByStatusAndUpdatedAtBefore("active", LocalDateTime.now());
        for (TargetingCampaign campaign : activeCampaigns) {
            try {
                syncInsights(campaign.getId());
            } catch (Exception e) {
                log.error("Failed to sync insights for {}", campaign.getId(), e);
            }
        }
    }

    public void syncInsights(String campaignId) {
        TargetingCampaign campaign = repository.findById(campaignId).orElseThrow();
        List<TargetingInsight> newInsights = insightService.generateTargetingInsights(campaign);
        if (campaign.getInsights() == null) campaign.setInsights(new ArrayList<>());
        campaign.getInsights().addAll(newInsights);
        
        // Accumulate metrics
        PerformanceMetrics metrics = campaign.getPerformanceMetrics() != null ? campaign.getPerformanceMetrics() : new PerformanceMetrics();
        for (TargetingInsight ti : newInsights) {
            if ("SPEND".equals(ti.getMetric())) metrics.setSpend((metrics.getSpend() != null ? metrics.getSpend() : 0) + ti.getValue());
        }
        campaign.setPerformanceMetrics(metrics);
        
        campaign.setUpdatedAt(LocalDateTime.now());
        repository.save(campaign);
    }

    private void failCampaign(TargetingCampaign campaign, String error) {
        campaign.setStatus("failed");
        addStatusHistory(campaign, "FAILED", error != null ? error : CampaignStatusMessage.FAILED.getMessage());
        campaign.setUpdatedAt(LocalDateTime.now());
        campaign.setCompletedAt(LocalDateTime.now());
        repository.save(campaign);
    }

    private void addStatusHistory(TargetingCampaign campaign, String status, String message) {
        if (campaign.getStatusHistory() == null) {
            campaign.setStatusHistory(new ArrayList<>());
        }
        campaign.getStatusHistory().add(StatusHistoryEntry.builder()
                .timestamp(LocalDateTime.now())
                .status(status)
                .message(message)
                .build());
    }

    private CampaignObjective resolveObjective(
            TargetingCampaignRequest req,
            TargetingRecommendationDto recommendation) {
        String objective = req.getObjective();
        if (objective == null || objective.isBlank()) {
            objective = recommendation != null ? recommendation.getCampaignObjective() : null;
        }
        if (objective == null || objective.isBlank()) {
            return CampaignObjective.LEADS;
        }
        try {
            return CampaignObjective.valueOf(objective.trim().toUpperCase(Locale.ROOT));
        } catch (IllegalArgumentException ex) {
            log.warn("Unknown campaign objective '{}', fallback to LEADS", objective);
            return CampaignObjective.LEADS;
        }
    }

    private String resolveCampaignName(
            TargetingCampaignRequest req,
            MarketingStrategy strategy,
            TargetingRecommendationDto recommendation) {
        if (req.getCampaignName() != null && !req.getCampaignName().isBlank()) {
            return req.getCampaignName().trim();
        }

        List<String> nameParts = new ArrayList<>();
        nameParts.add("Targeting");
        if (strategy.getScoringModelTitle() != null && !strategy.getScoringModelTitle().isBlank()) {
            nameParts.add(strategy.getScoringModelTitle().trim());
        }
        if (recommendation != null
                && recommendation.getRecommendedTypeLabel() != null
                && !recommendation.getRecommendedTypeLabel().isBlank()) {
            nameParts.add(recommendation.getRecommendedTypeLabel().trim());
        }
        return String.join(" | ", nameParts);
    }

    private List<kz.konturai.parser.enums.TargetingPlatform> resolvePlatforms(
            TargetingCampaignRequest req,
            TargetingRecommendationDto recommendation) {
        List<String> rawPlatforms = req.getPlatforms();
        if (rawPlatforms == null || rawPlatforms.isEmpty()) {
            rawPlatforms = recommendation != null ? recommendation.getRecommendedPlatforms() : Collections.emptyList();
        }
        if (rawPlatforms == null || rawPlatforms.isEmpty()) {
            return List.of(
                    kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM,
                    kz.konturai.parser.enums.TargetingPlatform.FACEBOOK
            );
        }

        return rawPlatforms.stream()
                .flatMap(p -> expandPlatform(p).stream())
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }

    private List<kz.konturai.parser.enums.TargetingPlatform> expandPlatform(String platform) {
        if (platform == null || platform.isBlank()) {
            return List.of();
        }
        String normalized = platform.trim().toUpperCase(Locale.ROOT);
        // FB_IG / META expand to both Facebook + Instagram
        if ("FB_IG".equals(normalized) || "FB+IG".equals(normalized)) {
            return List.of(
                    kz.konturai.parser.enums.TargetingPlatform.FACEBOOK,
                    kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM
            );
        }
        if ("META".equals(normalized)) {
            return List.of(
                    kz.konturai.parser.enums.TargetingPlatform.FACEBOOK,
                    kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM
            );
        }
        try {
            return List.of(kz.konturai.parser.enums.TargetingPlatform.valueOf(normalized));
        } catch (IllegalArgumentException ex) {
            log.warn("Unknown targeting platform '{}', skipping", platform);
            return List.of();
        }
    }

    private TargetingAIAnalysisResult buildAiAnalysisResult(
            MarketingStrategy strategy,
            MarketingAnalysisV3Document analysis,
            TargetingRecommendationDto recommendation) {
        return TargetingAIAnalysisResult.builder()
                .strategyId(strategy.getId())
                .analysisId(strategy.getAnalysisId())
                .businessNiche(analysis.getRequestData() != null ? analysis.getRequestData().getBusinessNiche() : null)
                .productName(analysis.getRequestData() != null ? analysis.getRequestData().getProductName() : null)
                .goal(analysis.getRequestData() != null ? analysis.getRequestData().getGoal() : null)
                .recommendedTargetingType(recommendation != null ? recommendation.getRecommendedType() : null)
                .recommendedTargetingTypeLabel(recommendation != null ? recommendation.getRecommendedTypeLabel() : null)
                .recommendedCampaignObjective(recommendation != null ? recommendation.getCampaignObjective() : null)
                .questionnaireSignals(recommendation != null ? recommendation.getQuestionnaireSignals() : List.of())
                .aiRecommendationsRationale(recommendation != null ? recommendation.getTypeRationale() : null)
                .quickWins(recommendation != null ? recommendation.getQuickWins() : List.of())
                .warnings(recommendation != null ? recommendation.getWarnings() : List.of())
                .technicalHealthSummary(buildTechnicalHealthSummary(recommendation))
                .lpMatchScore(recommendation != null && recommendation.getLpAudit() != null
                        ? recommendation.getLpAudit().getMatchScore() : null)
                .policyRiskLevel(recommendation != null && recommendation.getRisks() != null
                        ? recommendation.getRisks().getPolicyRiskLevel() : null)
                .suggestedAutomationRulesCount(recommendation != null && recommendation.getSuggestedRules() != null
                        ? recommendation.getSuggestedRules().size() : 0)
                .matrixCellsCount(recommendation != null
                        && recommendation.getHypothesisMatrix() != null
                        && recommendation.getHypothesisMatrix().getMatrix() != null
                        ? recommendation.getHypothesisMatrix().getMatrix().size() : 0)
                .targetingRecommendation(recommendation)
                .build();
    }

    private String buildTechnicalHealthSummary(TargetingRecommendationDto recommendation) {
        if (recommendation == null || recommendation.getPixelHealth() == null) {
            return null;
        }
        int enabled = 0;
        if (recommendation.getPixelHealth().isFbp()) enabled++;
        if (recommendation.getPixelHealth().isCapi()) enabled++;
        if (recommendation.getPixelHealth().isGtm()) enabled++;

        return switch (enabled) {
            case 3 -> "Strong";
            case 2 -> "Medium";
            default -> "Poor";
        };
    }

    private TargetingAudienceProfile buildAudienceProfile(
            TargetingRecommendationDto recommendation,
            kz.konturai.parser.dto.AudienceOverrideDto overrideDto) {
        AudienceProfileDto aiAudience = recommendation != null ? recommendation.getAudienceProfile() : null;

        int[] ageRange = parseAgeRange(aiAudience != null ? aiAudience.getAgeRange() : null);
        int ageMin = overrideDto != null && overrideDto.getAgeMin() > 0 ? overrideDto.getAgeMin() : ageRange[0];
        int ageMax = overrideDto != null && overrideDto.getAgeMax() > 0 ? overrideDto.getAgeMax() : ageRange[1];

        List<String> interests = new ArrayList<>();
        if (aiAudience != null && aiAudience.getInterests() != null) {
            interests.addAll(aiAudience.getInterests());
        }
        if (overrideDto != null && overrideDto.getAdditionalInterests() != null) {
            interests.addAll(overrideDto.getAdditionalInterests());
        }

        List<String> cities = new ArrayList<>();
        if (overrideDto != null && overrideDto.getCities() != null && !overrideDto.getCities().isEmpty()) {
            cities.addAll(overrideDto.getCities());
        } else if (aiAudience != null && aiAudience.getGeography() != null) {
            cities.addAll(aiAudience.getGeography());
        }

        List<String> genders = overrideDto != null && overrideDto.getGenders() != null && !overrideDto.getGenders().isEmpty()
                ? overrideDto.getGenders()
                : inferGenders(aiAudience != null ? aiAudience.getGender() : null);

        List<LocationTarget> locations = cities.stream()
                .filter(Objects::nonNull)
                .map(String::trim)
                .filter(city -> !city.isBlank())
                .distinct()
                .map(city -> LocationTarget.builder()
                        .cityName(city)
                        .country("KZ")
                        .radius(25.0)
                        .build())
                .collect(Collectors.toList());

        return TargetingAudienceProfile.builder()
                .ageMin(ageMin)
                .ageMax(ageMax)
                .genders(genders)
                .locations(locations)
                .interests(interests.stream().filter(Objects::nonNull).map(String::trim).filter(s -> !s.isBlank()).distinct().collect(Collectors.toList()))
                .behaviors(aiAudience != null && aiAudience.getBehaviors() != null ? aiAudience.getBehaviors() : List.of())
                .languages(splitLanguages(aiAudience != null ? aiAudience.getLanguage() : null))
                .excludedAudiences(aiAudience != null && aiAudience.getExclusions() != null ? aiAudience.getExclusions() : List.of())
                .deviceTypes(List.of("mobile", "desktop"))
                .connectionType("ALL")
                .audienceScore(85)
                .build();
    }

    private int[] parseAgeRange(String rawAgeRange) {
        if (rawAgeRange == null || rawAgeRange.isBlank()) {
            return new int[]{25, 45};
        }
        String[] parts = rawAgeRange.replaceAll("[^0-9-]", "").split("-");
        if (parts.length == 2) {
            try {
                return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1])};
            } catch (NumberFormatException ignored) {
                return new int[]{25, 45};
            }
        }
        return new int[]{25, 45};
    }

    private List<String> inferGenders(String genderText) {
        if (genderText == null || genderText.isBlank()) {
            return List.of("ALL");
        }
        String normalized = genderText.toLowerCase(Locale.ROOT);
        boolean hasWomen = normalized.contains("жен");
        boolean hasMen = normalized.contains("муж");
        if (hasWomen && hasMen) {
            return List.of("FEMALE", "MALE");
        }
        if (hasWomen) {
            return List.of("FEMALE");
        }
        if (hasMen) {
            return List.of("MALE");
        }
        return List.of("ALL");
    }

    private List<String> splitLanguages(String languagesText) {
        if (languagesText == null || languagesText.isBlank()) {
            return List.of("Русский");
        }
        return List.of(languagesText.split(","))
                .stream()
                .map(String::trim)
                .filter(s -> !s.isBlank())
                .distinct()
                .collect(Collectors.toList());
    }

    // ── AdSet enrichment helpers ──────────────────────────────────────────────

    /**
     * Определяет платформу AdSet'а по его названию или по платформам кампании.
     */
    private String resolvePlatformFromName(String name, TargetingCampaign campaign) {
        if (name == null) return "FACEBOOK";
        String n = name.toUpperCase(Locale.ROOT);
        if (n.contains("TIKTOK") || n.contains("ТИК")) return "TIKTOK";
        if (n.contains("INSTAGRAM") || n.contains("INSTA")) return "INSTAGRAM";
        if (n.contains("FACEBOOK") || n.contains("FB")) return "FACEBOOK";
        // Fallback — первая платформа из кампании
        if (campaign.getPlatforms() != null && !campaign.getPlatforms().isEmpty()) {
            return campaign.getPlatforms().get(0).name();
        }
        return "FACEBOOK";
    }

    /**
     * Определяет placements на основе платформы.
     */
    private List<String> resolvePlacements(String adSetName, TargetingCampaign campaign) {
        // Приоритет — placements из AI-рекомендаций
        if (campaign.getAiRecommendations() != null
                && campaign.getAiRecommendations().getTargetingRecommendation() != null) {
            List<String> all = campaign.getAiRecommendations()
                    .getTargetingRecommendation().getRecommendedPlacements();
            if (all != null && !all.isEmpty()) {
                String platform = resolvePlatformFromName(adSetName, campaign);
                return all.stream()
                        .filter(p -> p.toLowerCase(Locale.ROOT).contains(platform.toLowerCase(Locale.ROOT)))
                        .collect(Collectors.toList());
            }
        }
        String platform = resolvePlatformFromName(adSetName, campaign);
        return switch (platform) {
            case "INSTAGRAM" -> List.of("Instagram Feed", "Instagram Stories");
            case "TIKTOK"    -> List.of("TikTok Feed", "TikTok TopView");
            default          -> List.of("Facebook Feed", "Facebook Stories");
        };
    }

    /**
     * Определяет adFormat по расширению mediaUrl и количеству объявлений.
     */
    private String resolveAdFormat(String mediaUrl, int totalAdsInSet) {
        if (totalAdsInSet > 1) return "CAROUSEL";
        if (mediaUrl == null) return "SINGLE_IMAGE";
        String lower = mediaUrl.toLowerCase(Locale.ROOT);
        if (lower.endsWith(".mp4") || lower.endsWith(".mov")) return "VIDEO";
        return "SINGLE_IMAGE";
    }

    /**
     * Распределяет бюджет между AdSet'ами по фазам.
     * Фаза 1 → 30%, Фаза 2 → 40%, Фаза 3 → 30%. Остальные — равномерно.
     */
    private List<Double> distributeBudgetSlices(double total, int count) {
        if (count <= 0) return List.of();
        List<Double> slices = new ArrayList<>();
        if (count == 1) {
            slices.add(total);
        } else if (count == 2) {
            slices.add(total * 0.40);
            slices.add(total * 0.60);
        } else if (count == 3) {
            slices.add(total * 0.30);
            slices.add(total * 0.40);
            slices.add(total * 0.30);
        } else {
            double each = total / count;
            for (int i = 0; i < count; i++) slices.add(each);
        }
        return slices;
    }

    /**
     * Извлекает фазы из aiRecommendations кампании.
     */
    // ── Retry endpoint helper ─────────────────────────────────────────────────

    public TargetingCampaign retryCampaign(String campaignId, String userId) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        if (!"failed".equals(campaign.getStatus())) {
            throw new ValidationException("status", campaign.getStatus(),
                    "Повторный запуск возможен только для кампаний со статусом FAILED");
        }
        campaign.setStatus("draft");
        addStatusHistory(campaign, "CREATED", "Повторный запуск кампании после ошибки.");
        TargetingCampaign saved = repository.save(campaign);
        processAsync(saved.getId(), null, userId);
        return saved;
    }

    public TargetingCampaign updateBudget(String campaignId, String userId, Double totalBudget, Double dailyBudget) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        if (totalBudget != null && totalBudget > 0) campaign.getBudget().setTotalBudget(totalBudget);
        if (dailyBudget != null && dailyBudget > 0) campaign.getBudget().setDailyBudget(dailyBudget);
        campaign.setUpdatedAt(LocalDateTime.now());
        return repository.save(campaign);
    }

    public TargetingCampaign updateAudience(String campaignId, String userId, TargetingAudienceProfile audience) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        campaign.setAudience(audience);
        campaign.setUpdatedAt(LocalDateTime.now());
        return repository.save(campaign);
    }

    public List<TargetingAdSet> getAdSets(String campaignId, String userId) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        return campaign.getAdSets() != null ? campaign.getAdSets() : List.of();
    }

    public TargetingCampaign updateAdSet(String campaignId, String adSetId, String userId, TargetingAdSet updated) {
        TargetingCampaign campaign = getCampaignById(campaignId, userId);
        if (campaign.getAdSets() != null) {
            campaign.getAdSets().replaceAll(as -> as.getAdSetId().equals(adSetId) ? updated : as);
        }
        campaign.setUpdatedAt(LocalDateTime.now());
        return repository.save(campaign);
    }
}
