package kz.konturai.parser.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import kz.konturai.parser.config.TargetingApiConfig;
import kz.konturai.parser.dto.FacebookAdAccountDto;
import kz.konturai.parser.dto.InstagramInsightDto;
import kz.konturai.parser.exception.FacebookPermissionException;
import kz.konturai.parser.exception.FacebookTokenExpiredException;
import kz.konturai.parser.model.BudgetConfig;
import kz.konturai.parser.model.TargetingAd;
import kz.konturai.parser.model.TargetingAdSet;
import kz.konturai.parser.model.TargetingAudienceProfile;
import kz.konturai.parser.model.TargetingCampaign;
import kz.konturai.parser.enums.CampaignObjective;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;

import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

//@Service
@RequiredArgsConstructor
@Slf4j
public class FacebookAdsService {

    private final TargetingApiConfig apiConfig;
    private final WebClient.Builder webClientBuilder;
    private final SocialMediaCredentialsService credentialsService;
    private final ObjectMapper objectMapper;

    private WebClient webClient;
    private final Map<String, List<Map<String, Object>>> interestCache = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        String baseUrl = "https://graph.facebook.com/" + apiConfig.getFacebook().getGraphApiVersion();
        this.webClient = webClientBuilder.baseUrl(baseUrl).build();
    }

    public List<FacebookAdAccountDto> getAdAccounts(String accessToken) {
        return executeWithRetry(() -> {
            JsonNode response = webClient.get()
                    .uri("/me/adaccounts?fields=id,name,account_status,currency,timezone_name,balance,amount_spent")
                    .header("Authorization", "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();
            List<FacebookAdAccountDto> accounts = new ArrayList<>();
            if (response != null && response.has("data")) {
                for (JsonNode node : response.get("data")) {
                    accounts.add(objectMapper.convertValue(node, FacebookAdAccountDto.class));
                }
            }
            return accounts;
        });
    }

    public String createCampaign(String accessToken, String adAccountId, TargetingCampaign campaign) {
        return executeWithRetry(() -> {
            Map<String, Object> body = new HashMap<>();
            body.put("name", campaign.getName());
            body.put("objective", mapObjective(campaign.getObjective()));
            body.put("status", "PAUSED");
            body.put("special_ad_categories", new ArrayList<>());

            JsonNode response = webClient.post()
                    .uri("/{ad-account-id}/campaigns", adAccountId)
                    .header("Authorization", "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();

            return response != null && response.has("id") ? response.get("id").asText() : null;
        });
    }

    public String createAdSet(String accessToken, String adAccountId, String facebookCampaignId,
                              TargetingAdSet adSet, TargetingAudienceProfile audience, BudgetConfig budget) {
        return executeWithRetry(() -> {
            Map<String, Object> body = new HashMap<>();
            body.put("name", adSet.getName());
            body.put("campaign_id", facebookCampaignId);
            body.put("status", "PAUSED");

            Map<String, Object> targeting = new HashMap<>();
            targeting.put("geo_locations", Map.of("countries", List.of("KZ")));
            if (audience.getAgeMin() > 0) targeting.put("age_min", audience.getAgeMin());
            if (audience.getAgeMax() > 0) targeting.put("age_max", audience.getAgeMax());
            body.put("targeting", targeting);

            body.put("optimization_goal", adSet.getOptimizationGoal());
            body.put("billing_event", "IMPRESSIONS");
            
            double budgetInCents = budget.getDailyBudget() / apiConfig.getKztUsdRate() * 100;
            body.put("daily_budget", (long) budgetInCents);

            JsonNode response = webClient.post()
                    .uri("/{ad-account-id}/adsets", adAccountId)
                    .header("Authorization", "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();

            return response != null && response.has("id") ? response.get("id").asText() : null;
        });
    }

    public String createAd(String accessToken, String adAccountId, String facebookAdSetId, TargetingAd ad, String pageId) {
        return executeWithRetry(() -> {
            // Placeholder for AdCreative creation logic
            String creativeId = "creative_placeholder"; 

            Map<String, Object> body = new HashMap<>();
            body.put("name", ad.getName());
            body.put("adset_id", facebookAdSetId);
            body.put("creative", Map.of("creative_id", creativeId));
            body.put("status", "PAUSED");

            JsonNode response = webClient.post()
                    .uri("/{ad-account-id}/ads", adAccountId)
                    .header("Authorization", "Bearer " + accessToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();

            return response != null && response.has("id") ? response.get("id").asText() : null;
        });
    }

    public InstagramInsightDto getInsights(String accessToken, String campaignId, String datePreset) {
        return executeWithRetry(() -> {
            JsonNode response = webClient.get()
                    .uri("/{campaign-id}/insights?fields=impressions,reach,clicks,spend,ctr,cpm,cpc,actions,cost_per_action_type&date_preset={date_preset}", campaignId, datePreset)
                    .header("Authorization", "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();
            
            if (response != null && response.has("data") && response.get("data").isArray() && !response.get("data").isEmpty()) {
                JsonNode data = response.get("data").get(0);
                return InstagramInsightDto.builder()
                        .impressions(data.has("impressions") ? data.get("impressions").asLong() : 0L)
                        .reach(data.has("reach") ? data.get("reach").asLong() : 0L)
                        .clicks(data.has("clicks") ? data.get("clicks").asLong() : 0L)
                        .spend(data.has("spend") ? data.get("spend").asDouble() : 0.0)
                        .ctr(data.has("ctr") ? data.get("ctr").asDouble() : 0.0)
                        .cpm(data.has("cpm") ? data.get("cpm").asDouble() : 0.0)
                        .cpc(data.has("cpc") ? data.get("cpc").asDouble() : 0.0)
                        .build();
            }
            return new InstagramInsightDto();
        });
    }

    public InstagramInsightDto getInstagramInsights(String accessToken, String instagramAccountId) {
        return executeWithRetry(() -> {
            JsonNode response = webClient.get()
                    .uri("/{ig-user-id}/insights?metric=impressions,reach,profile_views,website_clicks,follower_count&period=week", instagramAccountId)
                    .header("Authorization", "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();
            return new InstagramInsightDto();
        });
    }

    public List<Map<String, Object>> searchTargetingInterests(String accessToken, String query) {
        if (interestCache.containsKey(query)) {
            return interestCache.get(query);
        }
        return executeWithRetry(() -> {
            JsonNode response = webClient.get()
                    .uri("/search?type=adinterest&q={query}&locale=ru_RU", query)
                    .header("Authorization", "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();
            
            List<Map<String, Object>> results = new ArrayList<>();
            if (response != null && response.has("data")) {
                for (JsonNode node : response.get("data")) {
                    results.add(objectMapper.convertValue(node, Map.class));
                }
            }
            interestCache.put(query, results);
            return results;
        });
    }

    public void pauseCampaign(String accessToken, String campaignId) {
        updateCampaignStatus(accessToken, campaignId, "PAUSED");
    }

    public void resumeCampaign(String accessToken, String campaignId) {
        updateCampaignStatus(accessToken, campaignId, "ACTIVE");
    }

    private void updateCampaignStatus(String accessToken, String campaignId, String status) {
        executeWithRetry(() -> {
            webClient.post()
                    .uri("/{campaign-id}?status={status}", campaignId, status)
                    .header("Authorization", "Bearer " + accessToken)
                    .retrieve()
                    .bodyToMono(String.class)
                    .block();
            return null;
        });
    }

    public String getOAuthUrl(String userId) {
        return "https://www.facebook.com/" + apiConfig.getFacebook().getGraphApiVersion() + "/dialog/oauth?" +
                "client_id=" + apiConfig.getFacebook().getAppId() +
                "&redirect_uri=" + apiConfig.getFacebook().getOauthRedirectUri() +
                "&scope=ads_management,ads_read,instagram_basic,instagram_manage_insights,pages_read_engagement" +
                "&state=" + userId +
                "&response_type=code";
    }

    public String exchangeCodeForToken(String code, String userId) {
        return executeWithRetry(() -> {
            JsonNode response = webClient.get()
                    .uri("/oauth/access_token?client_id={app_id}&client_secret={app_secret}&code={code}&redirect_uri={redirect_uri}",
                            apiConfig.getFacebook().getAppId(),
                            apiConfig.getFacebook().getAppSecret(),
                            code,
                            apiConfig.getFacebook().getOauthRedirectUri())
                    .retrieve()
                    .bodyToMono(JsonNode.class)
                    .block();

            if (response != null && response.has("access_token")) {
                String accessToken = response.get("access_token").asText();
                credentialsService.saveCredentials(userId, "FACEBOOK", accessToken);
                return accessToken;
            }
            throw new RuntimeException("Failed to exchange code for token");
        });
    }

    private String mapObjective(CampaignObjective objective) {
        if (objective == null) return "OUTCOME_TRAFFIC";
        return switch (objective) {
            case LEADS -> "OUTCOME_LEADS";
            case SALES -> "OUTCOME_SALES";
            case TRAFFIC -> "OUTCOME_TRAFFIC";
            case AWARENESS -> "OUTCOME_AWARENESS";
            case ENGAGEMENT -> "OUTCOME_ENGAGEMENT";
            case APP_INSTALLS -> "OUTCOME_APP_PROMOTION";
        };
    }

    private <T> T executeWithRetry(java.util.function.Supplier<T> action) {
        int maxRetries = apiConfig.getFacebook().getMaxRetries();
        long retryDelay = apiConfig.getFacebook().getRateLimitRetryMs();
        int attempt = 0;

        while (true) {
            try {
                return action.get();
            } catch (WebClientResponseException e) {
                handleGraphApiError(e);
                attempt++;
                if (attempt > maxRetries) {
                    throw e;
                }
                log.warn("[Facebook API] Rate limit hit. Retrying in {} ms. Attempt {}/{}", retryDelay, attempt, maxRetries);
                try {
                    Thread.sleep(retryDelay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Thread interrupted during retry delay", ie);
                }
            } catch (Exception e) {
                log.error("[Facebook API] Unexpected error", e);
                throw e;
            }
        }
    }

    private void handleGraphApiError(WebClientResponseException e) {
        String responseBody = e.getResponseBodyAsString();
        log.error("[Facebook API] Error: Status={}, Response={}", e.getStatusCode(), responseBody);

        try {
            JsonNode errorNode = objectMapper.readTree(responseBody).path("error");
            if (!errorNode.isMissingNode()) {
                int code = errorNode.path("code").asInt();
                if (code == 190) {
                    throw new FacebookTokenExpiredException("Facebook token expired or invalid", "Facebook token expired or invalid", 190, 190);
                } else if (code == 200 || code == 270) {
                    throw new FacebookPermissionException("Permission denied for this action");
                } else if (code == 17 || code == 80000 || code == 32 || code == 4) {
                    // Rate limit, let the caller handle retry
                    return; 
                }
            }
        } catch (Exception parseException) {
            log.warn("Failed to parse Facebook API error response");
        }
        throw e;
    }
}
