package kz.konturai.parser.service;

import com.fasterxml.jackson.databind.JsonNode;
import kz.konturai.parser.config.FacebookLeadProperties;
import kz.konturai.parser.model.FacebookLead;
import kz.konturai.parser.repository.FacebookLeadRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Locale;

@Service
public class FacebookLeadCollectorService {

    private static final Logger log = LoggerFactory.getLogger(FacebookLeadCollectorService.class);

    private static final DateTimeFormatter FACEBOOK_TIMESTAMP_FORMAT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");

    private static final List<String> HOT_LEAD_KEYWORDS = List.of(
            normalizeForSearch("адрес"),
            normalizeForSearch("филиал"),
            normalizeForSearch("где вы"),
            normalizeForSearch("где находит"),
            normalizeForSearch("как доехать"),
            normalizeForSearch("график"),
            normalizeForSearch("режим"),
            normalizeForSearch("со скольки"),
            normalizeForSearch("до скольки"),
            normalizeForSearch("работает"),
            normalizeForSearch("открыты"),
            normalizeForSearch("доставк"),
            normalizeForSearch("заказ"),
            normalizeForSearch("меню"),
            normalizeForSearch("привез"),
            normalizeForSearch("оформить"),
            normalizeForSearch("яндекс"),
            normalizeForSearch("wolt"),
            normalizeForSearch("glovo"),
            normalizeForSearch("цен"),
            normalizeForSearch("стоит"),
            normalizeForSearch("прайс"),
            normalizeForSearch("каспи"),
            normalizeForSearch("qr"),
            normalizeForSearch("red"),
            normalizeForSearch("лс"),
            normalizeForSearch("директ"),
            normalizeForSearch("личк"),
            normalizeForSearch("номер"),
            normalizeForSearch("телефон")
    );

    private final FacebookLeadProperties properties;
    private final FacebookLeadGraphApiClient graphApiClient;
    private final FacebookLeadRepository facebookLeadRepository;
    private final FacebookLeadCallCenterSyncService callCenterSyncService;

    public FacebookLeadCollectorService(
            FacebookLeadProperties properties,
            FacebookLeadGraphApiClient graphApiClient,
            FacebookLeadRepository facebookLeadRepository,
            FacebookLeadCallCenterSyncService callCenterSyncService) {
        this.properties = properties;
        this.graphApiClient = graphApiClient;
        this.facebookLeadRepository = facebookLeadRepository;
        this.callCenterSyncService = callCenterSyncService;
    }

    @PostConstruct
    void logConfiguration() {
        log.info("Facebook hot lead collector initialized. pageId={}, accessTokenPresent={}, cron={}, graphApiVersion={}, postsPageSize={}, commentsPageSize={}",
                properties.getPageId(),
                hasAccessToken(),
                properties.getCron(),
                properties.getGraphApiVersion(),
                properties.getPostsPageSize(),
                properties.getCommentsPageSize());
    }

    @EventListener(ApplicationReadyEvent.class)
    public void collectHotLeadsOnStartup() {
        runCollection("startup");
    }

    @Scheduled(cron = "${facebook.leads.cron:0 */15 * * * *}")
    public void collectHotLeadsScheduled() {
        runCollection("scheduled");
    }

    private void runCollection(String trigger) {
        log.info("Facebook hot lead trigger [{}] received. pageId={}, accessTokenPresent={}, cron={}",
                trigger, properties.getPageId(), hasAccessToken(), properties.getCron());

        if (!isConfigured()) {
            log.warn("Facebook lead collection [{}] skipped: pageId/accessToken are not configured. pageId={}, accessTokenPresent={}, expectedEnvVar=FACEBOOK_LEADS_ACCESS_TOKEN",
                    trigger, properties.getPageId(), hasAccessToken());
            return;
        }

        log.info("Starting Facebook hot lead collection [{}] for page {}. postsPageSize={}, commentsPageSize={}",
                trigger, properties.getPageId(), properties.getPostsPageSize(), properties.getCommentsPageSize());

        try {
            callCenterSyncService.syncPendingLeads();
            int newLeadsCount = collectHotLeads();
            log.info("Facebook hot lead collection [{}] finished. New leads found: {}",
                    trigger, newLeadsCount);
        } catch (Exception e) {
            log.error("Facebook hot lead collection [{}] failed for page {}", trigger, properties.getPageId(), e);
        }
    }

    public int collectHotLeads() {
        if (!isConfigured()) {
            log.warn("Facebook lead collection skipped: pageId/accessToken are not configured");
            return 0;
        }

        int newLeadsCount = 0;
        String postsAfterCursor = null;
        int postsPageNumber = 1;

        do {
            log.info("Processing Facebook posts page {}. afterCursor={}",
                    postsPageNumber, formatCursor(postsAfterCursor));
            JsonNode postsPage = graphApiClient.fetchPostsPage(postsAfterCursor);
            int pageLeadsCount = processPosts(postsPage.path("data"));
            newLeadsCount += pageLeadsCount;
            postsAfterCursor = extractAfterCursor(postsPage);
            log.info("Processed Facebook posts page {}. New leads on page: {}. nextAfterCursor={}",
                    postsPageNumber, pageLeadsCount, formatCursor(postsAfterCursor));
            postsPageNumber++;
        } while (StringUtils.hasText(postsAfterCursor));

        log.info("Facebook hot lead collection summary: totalNewLeads={}", newLeadsCount);
        return newLeadsCount;
    }

    private int processPosts(JsonNode posts) {
        if (!posts.isArray()) {
            log.warn("Facebook posts payload does not contain an array of posts");
            return 0;
        }

        int newLeadsCount = 0;
        log.info("Facebook posts page contains {} post(s)", posts.size());

        for (JsonNode post : posts) {
            newLeadsCount += processPostComments(post);
        }

        return newLeadsCount;
    }

    private int processPostComments(JsonNode post) {
        String postId = post.path("id").asText(null);
        log.info("Processing Facebook post {} comments", StringUtils.hasText(postId) ? postId : "<unknown>");

        if (!StringUtils.hasText(postId)) {
            log.warn("Skipping Facebook post without id while collecting comments");
            return 0;
        }

        JsonNode firstCommentsPage = graphApiClient.fetchCommentsPage(postId, null);
        int newLeadsCount = saveMatchingComments(firstCommentsPage.path("data"));
        String commentsAfterCursor = extractAfterCursor(firstCommentsPage);
        int commentsPageNumber = 2;

        while (StringUtils.hasText(commentsAfterCursor)) {
            log.info("Processing extra comments page {} for post {}. afterCursor={}",
                    commentsPageNumber, postId, formatCursor(commentsAfterCursor));
            JsonNode commentsPage = graphApiClient.fetchCommentsPage(postId, commentsAfterCursor);
            int pageLeadsCount = saveMatchingComments(commentsPage.path("data"));
            newLeadsCount += pageLeadsCount;
            commentsAfterCursor = extractAfterCursor(commentsPage);
            log.info("Processed extra comments page {} for post {}. New leads on page: {}. nextAfterCursor={}",
                    commentsPageNumber, postId, pageLeadsCount, formatCursor(commentsAfterCursor));
            commentsPageNumber++;
        }

        log.info("Finished processing post {}. New leads found: {}",
                StringUtils.hasText(postId) ? postId : "<unknown>", newLeadsCount);
        return newLeadsCount;
    }

    private int saveMatchingComments(JsonNode comments) {
        if (!comments.isArray()) {
            log.info("No comments array found in current Facebook payload chunk");
            return 0;
        }

        int savedCount = 0;
        log.info("Inspecting {} comment(s) for hot lead keywords", comments.size());

        for (JsonNode comment : comments) {
            String externalCommentId = comment.path("id").asText(null);
            String message = comment.path("message").asText("");
            String authorName = comment.path("from").path("name").asText("Unknown");

            if (!StringUtils.hasText(externalCommentId) || !StringUtils.hasText(message)) {
                log.info("Skipping Facebook comment without id/message. commentId={}, author={}",
                        externalCommentId, authorName);
                continue;
            }

            if (!containsHotLeadKeyword(message)) {
                continue;
            }

            String matchedKeyword = findMatchedKeyword(message);

            log.info("Hot Facebook comment detected. commentId={}, author={}, matchedKeyword='{}', message='{}'",
                    externalCommentId, authorName, matchedKeyword, shorten(message));

            if (facebookLeadRepository.existsByExternalCommentId(externalCommentId)) {
                log.info("Skipping duplicate Facebook lead. commentId={}", externalCommentId);
                continue;
            }

            try {
                FacebookLead savedLead = facebookLeadRepository.save(FacebookLead.builder()
                        .externalCommentId(externalCommentId)
                        .authorName(authorName)
                        .message(message)
                        .timestamp(parseTimestamp(comment.path("created_time").asText(null)))
                        .build());
                savedCount++;
                log.info("Saved new Facebook lead. commentId={}, author={}, status=NEW",
                        externalCommentId, authorName);
                callCenterSyncService.syncLead(savedLead);
            } catch (DuplicateKeyException e) {
                log.info("Facebook lead already exists by unique index. commentId={}", externalCommentId);
            }
        }

        log.info("Finished inspecting current comment chunk. Saved new leads: {}", savedCount);
        return savedCount;
    }

    static boolean containsHotLeadKeyword(String message) {
        return findMatchedKeyword(message) != null;
    }

    private static String findMatchedKeyword(String message) {
        String normalizedMessage = normalizeForSearch(message);
        return HOT_LEAD_KEYWORDS.stream()
                .filter(normalizedMessage::contains)
                .findFirst()
                .orElse(null);
    }

    private LocalDateTime parseTimestamp(String createdTime) {
        if (!StringUtils.hasText(createdTime)) {
            return LocalDateTime.now();
        }

        try {
            return OffsetDateTime.parse(createdTime).toLocalDateTime();
        } catch (DateTimeParseException e) {
            try {
                return OffsetDateTime.parse(createdTime, FACEBOOK_TIMESTAMP_FORMAT).toLocalDateTime();
            } catch (DateTimeParseException ignored) {
                log.warn("Failed to parse Facebook comment timestamp '{}'. Falling back to now()", createdTime);
                return LocalDateTime.now();
            }
        }
    }

    private String extractAfterCursor(JsonNode node) {
        String afterCursor = node.path("paging").path("cursors").path("after").asText(null);
        return StringUtils.hasText(afterCursor) ? afterCursor : null;
    }

    private boolean isConfigured() {
        return StringUtils.hasText(properties.getPageId()) && StringUtils.hasText(properties.getAccessToken());
    }

    private boolean hasAccessToken() {
        return StringUtils.hasText(properties.getAccessToken());
    }

    private String formatCursor(String cursor) {
        return StringUtils.hasText(cursor) ? cursor : "<first-page>";
    }

    private String shorten(String message) {
        if (!StringUtils.hasText(message)) {
            return "";
        }
        return message.length() <= 120 ? message : message.substring(0, 117) + "...";
    }

    static String normalizeForSearch(String text) {
        if (!StringUtils.hasText(text)) {
            return "";
        }

        return text
                .toLowerCase(Locale.ROOT)
                .replace('ё', 'е')
                .replaceAll("\\s+", " ")
                .trim();
    }
}
