package com.ttstd.signaling;

import com.ttstd.signaling.model.DeviceBinding;
import com.ttstd.signaling.repository.UserAccountRepository;
import com.ttstd.signaling.service.AuditService;
import com.ttstd.signaling.service.BindingService;
import com.ttstd.signaling.store.BindingStore;
import com.ttstd.signaling.store.BlacklistStore;
import com.ttstd.signaling.store.InMemoryBindingStore;
import com.ttstd.signaling.store.InMemoryBlacklistStore;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.*;

class BindingServiceTest {

    private BindingService bindingService;
    private UserAccountRepository userRepo;

    @BeforeEach
    void setUp() {
        BindingStore bindingStore = new InMemoryBindingStore();
        BlacklistStore blacklistStore = new InMemoryBlacklistStore();
        userRepo = Mockito.mock(UserAccountRepository.class);
        bindingService = new BindingService(bindingStore, blacklistStore, userRepo, new AuditService());
    }

    @Test
    void bindThenIsBound() {
        bindingService.bind("dev_1", "usr_1", DeviceBinding.BindingRole.MEMBER, "客厅", "device:dev_1");
        assertTrue(bindingService.isBound("dev_1", "usr_1"));
    }

    @Test
    void unboundIsNotBound() {
        assertFalse(bindingService.isBound("dev_2", "usr_2"));
    }

    @Test
    void revokeThenNotBound() {
        bindingService.bind("dev_3", "usr_3", DeviceBinding.BindingRole.MEMBER, null, "admin");
        bindingService.revokeBinding("dev_3", "usr_3", "admin");
        assertFalse(bindingService.isBound("dev_3", "usr_3"));
    }

    @Test
    void rebindAfterRevokeRestoresActive() {
        bindingService.bind("dev_4", "usr_4", DeviceBinding.BindingRole.MEMBER, null, "admin");
        bindingService.revokeBinding("dev_4", "usr_4", "admin");
        assertFalse(bindingService.isBound("dev_4", "usr_4"));
        bindingService.bind("dev_4", "usr_4", DeviceBinding.BindingRole.OWNER, "again", "admin");
        assertTrue(bindingService.isBound("dev_4", "usr_4"));
    }

    @Test
    void blacklistOverridesBinding() {
        bindingService.bind("dev_5", "usr_5", DeviceBinding.BindingRole.MEMBER, null, "admin");
        assertTrue(bindingService.isBound("dev_5", "usr_5"));
        bindingService.addBlacklist("dev_5", "usr_5", "骚扰", "device:dev_5");
        assertTrue(bindingService.isBlacklisted("dev_5", "usr_5"));
    }

    @Test
    void notBlacklistedByDefault() {
        assertFalse(bindingService.isBlacklisted("dev_6", "usr_6"));
    }

    @Test
    void removeBlacklistWorks() {
        bindingService.addBlacklist("dev_7", "usr_7", "reason", "admin");
        assertTrue(bindingService.isBlacklisted("dev_7", "usr_7"));
        bindingService.removeBlacklist("dev_7", "usr_7", "admin");
        assertFalse(bindingService.isBlacklisted("dev_7", "usr_7"));
    }

    @Test
    void listByUserReturnsOnlyBindings() {
        bindingService.bind("dev_a", "usr_x", DeviceBinding.BindingRole.MEMBER, null, "admin");
        bindingService.bind("dev_b", "usr_x", DeviceBinding.BindingRole.MEMBER, null, "admin");
        bindingService.bind("dev_c", "usr_y", DeviceBinding.BindingRole.MEMBER, null, "admin");
        assertEquals(2, bindingService.listByUser("usr_x").size());
        assertEquals(1, bindingService.listByUser("usr_y").size());
    }

    @Test
    void resolveUserIdUsesRepository() {
        com.ttstd.signaling.model.UserAccount u = Mockito.mock(com.ttstd.signaling.model.UserAccount.class);
        Mockito.when(u.getUserId()).thenReturn("usr_resolved");
        Mockito.when(userRepo.findByUsername("alice")).thenReturn(java.util.Optional.of(u));
        assertEquals("usr_resolved", bindingService.resolveUserId("alice"));
    }

    @Test
    void resolveUserIdThrowsForUnknown() {
        Mockito.when(userRepo.findByUsername("nobody")).thenReturn(java.util.Optional.empty());
        assertThrows(com.ttstd.signaling.security.AuthException.class,
                () -> bindingService.resolveUserId("nobody"));
    }
}
