package com.alttd.altitudeweb.services.chat;

import com.alttd.altitudeweb.controllers.chat.ChatMessage;
import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
import com.alttd.altitudeweb.model.PermissionClaimDto;
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
import com.alttd.altitudeweb.setup.Connection;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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 java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChatService {

    private static final Duration MAX_AGE = Duration.ofHours(1);
    @Value("${chat.allowed-servers}")
    private String[] allowedServers;
    private final NavigableMap<Instant, ChatMessage> chatMessages = new TreeMap<>();
    private final EventPublisher eventPublisher;
    private long lastMillis = 0;

    @Scheduled(cron = "0 * * * * *")
    public void clearOldMessages() {
        Instant cutoff = Instant.now().minus(MAX_AGE);
        int currentSize = chatMessages.size();

        chatMessages.headMap(cutoff, false).clear();

        log.info("Clearing old chat messages. Removed {}/{} messages",
                currentSize - chatMessages.size(), currentSize);
    }

    @EventListener(ApplicationReadyEvent.class)
    public void loadMessagesFromDb() {
        Connection.getConnection(Databases.CHAT)
                .runQuery(sqlSession -> {
                    log.debug("Loading uuid search result count");
                    try {
                        sqlSession.getMapper(ChatLogMapper.class)
                                .getChatLogs(Instant.now().minus(Duration.ofHours(1)).toEpochMilli())
                                .stream()
                                .map(ChatMessageMapper::fromDao)
                                .forEach(this::putChatMessage);
                    } catch (Exception e) {
                        log.error("Failed to load history count", e);
                    }
                });
    }

    private synchronized void setUniqueTimestamp(ChatMessage chatMessage) {
        long timestamp = chatMessage.getTimestamp().toEpochMilli();
        if (timestamp == lastMillis) {
            timestamp += 1;
        } else {
            return;
        }
        lastMillis = timestamp;
        chatMessage.setTimestamp(Instant.ofEpochMilli(timestamp));
    }

    private void putChatMessage(ChatMessage chatMessage) {
        setUniqueTimestamp(chatMessage);
        chatMessages.put(chatMessage.getTimestamp(), chatMessage);
    }

    public void addChatMessage(List<ChatMessage> chatMessageList) {
        chatMessageList.forEach(this::putChatMessage);
        sendMessagesToListeners(chatMessageList);
    }

    private void sendMessagesToListeners(List<ChatMessage> chatMessageList) {
        Map<ChatMessage, String> jsonCache = new HashMap<>();
        for (ChatMessage message : chatMessageList) {
            jsonCache.put(message, ChatMessageMapper.toJson(message));
        }

        eventPublisher.sendToUsers("chat", (eventUser) ->
                chatMessageList.stream()
                        .filter(chatMessage -> shouldReceive(eventUser, chatMessage))
                        .map(jsonCache::get)
                        .collect(Collectors.joining(",", "[", "]"))
        );
    }

    private boolean shouldReceive(EventUser eventUser, ChatMessage chatMessage) {
        if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) {
            return true;
        }

        if (!Arrays.asList(allowedServers).contains(chatMessage.getServer())) {
            return false;
        }

        if (!chatMessage.isNotBlocked()) {
            return false;
        }

        return switch (chatMessage.getType()) {
            case PUBLIC, GLOBAL -> true;
            case PARTY -> {
                Integer partyId = eventUser.partyId();
                if (partyId == null) {
                    yield false;
                }
                yield chatMessage.getChannel().equals(String.valueOf(partyId));
            }
            case MSG -> eventUser.uuid().toString().equals(chatMessage.getReceiver());
            case GAC -> eventUser.hasPermission(PermissionClaimDto.MOD);
            case CUSTOM -> false;
        };
    }

    public List<ChatMessage> getMessagesSince(Instant instant) {
        return chatMessages.tailMap(instant, false).values().stream().toList();
    }

}
