package com.alttd.altitudeweb.services.chat;

import com.alttd.altitudeweb.controllers.chat.ChatMessage;
import com.alttd.altitudeweb.controllers.chat.ChatMessageType;
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.services.chat.event_publisher.MessageForUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.Instant;
import java.util.List;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

class ChatServiceTest {

    private ChatService chatService;
    private EventPublisher eventPublisher;

    @BeforeEach
    void setUp() {
        eventPublisher = mock(EventPublisher.class);
        chatService = new ChatService(eventPublisher);
        ReflectionTestUtils.setField(chatService, "allowedServers", new String[]{"server1"});
    }

    @Test
    void testPublicMessageSentToEveryone() {
        ChatMessage message = ChatMessage.builder()
                .uuid(UUID.randomUUID())
                .type(ChatMessageType.PUBLIC)
                .server("server1")
                .messageJson("hello")
                .notBlocked(true)
                .timestamp(Instant.now())
                .build();

        chatService.addChatMessage(List.of(message));

        ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
        verify(eventPublisher).sendToUsers(anyString(), captor.capture());

        MessageForUser messageForUser = captor.getValue();
        EventUser user = new EventUser(UUID.randomUUID(), List.of(), null);
        String result = messageForUser.get(user);

        assertTrue(result.contains("hello"));
    }

    @Test
    void testGacMessageSentOnlyToStaff() {
        ChatMessage message = ChatMessage.builder()
                .uuid(UUID.randomUUID())
                .type(ChatMessageType.GAC)
                .server("server1")
                .messageJson("staff chat")
                .notBlocked(true)
                .timestamp(Instant.now())
                .build();

        chatService.addChatMessage(List.of(message));

        ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
        verify(eventPublisher).sendToUsers(anyString(), captor.capture());

        MessageForUser messageForUser = captor.getValue();

        EventUser regularUser = new EventUser(UUID.randomUUID(), List.of(), null);
        assertFalse(messageForUser.get(regularUser).contains("staff chat"), "Regular user should not see GAC message");

        EventUser modUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.MOD.getValue()), null);
        assertTrue(messageForUser.get(modUser).contains("staff chat"), "MOD should see GAC message");
    }

    @Test
    void testMsgMessageSentToSenderAndReceiver() {
        UUID senderUuid = UUID.randomUUID();
        UUID receiverUuid = UUID.randomUUID();
        ChatMessage message = ChatMessage.builder()
                .uuid(senderUuid)
                .type(ChatMessageType.MSG)
                .server("server1")
                .receiver(receiverUuid.toString())
                .messageJson("private message")
                .notBlocked(true)
                .timestamp(Instant.now())
                .build();

        chatService.addChatMessage(List.of(message));

        ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
        verify(eventPublisher).sendToUsers(anyString(), captor.capture());

        MessageForUser messageForUser = captor.getValue();

        EventUser sender = new EventUser(senderUuid, List.of(), null);
        EventUser receiver = new EventUser(receiverUuid, List.of(), null);
        EventUser other = new EventUser(UUID.randomUUID(), List.of(), null);

        assertTrue(messageForUser.get(receiver).contains("private message"), "Receiver should see the message");
        assertFalse(messageForUser.get(other).contains("private message"), "Others should not see private message");
    }

    @Test
    void testGlobalMessageSentToEveryone() {
        ChatMessage message = ChatMessage.builder()
                .uuid(UUID.randomUUID())
                .type(ChatMessageType.GLOBAL)
                .server("server1")
                .messageJson("global hello")
                .notBlocked(true)
                .timestamp(Instant.now())
                .build();

        chatService.addChatMessage(List.of(message));

        ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
        verify(eventPublisher).sendToUsers(anyString(), captor.capture());

        MessageForUser messageForUser = captor.getValue();
        EventUser user = new EventUser(UUID.randomUUID(), List.of(), null);
        assertTrue(messageForUser.get(user).contains("global hello"));
    }

    @Test
    void testBlockedMessageOnlySentToHeadMod() {
        ChatMessage message = ChatMessage.builder()
                .uuid(UUID.randomUUID())
                .type(ChatMessageType.PUBLIC)
                .server("server1")
                .messageJson("blocked message")
                .notBlocked(false)
                .timestamp(Instant.now())
                .build();

        chatService.addChatMessage(List.of(message));

        ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
        verify(eventPublisher).sendToUsers(anyString(), captor.capture());

        MessageForUser messageForUser = captor.getValue();
        EventUser regularUser = new EventUser(UUID.randomUUID(), List.of(), null);
        EventUser headModUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.HEAD_MOD.getValue()), null);

        assertFalse(messageForUser.get(regularUser).contains("blocked message"));
        assertTrue(messageForUser.get(headModUser).contains("blocked message"));
    }
}
