package kz.konturai.parser.service;

import kz.konturai.parser.model.SocialMediaCredentials;
import kz.konturai.parser.repository.SocialMediaCredentialsRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SocialMediaCredentialsServiceTest {

    @Mock
    private SocialMediaCredentialsRepository repository;

    @Mock
    private EncryptionService encryptionService;

    @InjectMocks
    private SocialMediaCredentialsService service;

    @Test
    void saveCredentialsShouldThrowWhenInputIsInvalid() {
        assertThrows(IllegalArgumentException.class, () -> service.saveCredentials(null, "facebook", "token"));
        assertThrows(IllegalArgumentException.class, () -> service.saveCredentials("user-1", null, "token"));
        assertThrows(IllegalArgumentException.class, () -> service.saveCredentials("user-1", "facebook", ""));
    }

    @Test
    void saveCredentialsShouldCreateNewEntityAndEncryptCredentials() {
        when(repository.findByUserIdAndPlatform("user-1", "Facebook")).thenReturn(Optional.empty());
        when(encryptionService.encrypt("plain-token")).thenReturn("encrypted-token");
        when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));

        SocialMediaCredentials saved = service.saveCredentials("user-1", "Facebook", "plain-token");

        ArgumentCaptor<SocialMediaCredentials> captor = ArgumentCaptor.forClass(SocialMediaCredentials.class);
        verify(repository).save(captor.capture());
        SocialMediaCredentials persisted = captor.getValue();
        assertEquals("user-1", persisted.getUserId());
        assertEquals("facebook", persisted.getPlatform());
        assertEquals("encrypted-token", persisted.getEncryptedCredentials());
        assertSame(persisted, saved);
    }

    @Test
    void saveCredentialsShouldUpdateExistingEntity() {
        SocialMediaCredentials existing = new SocialMediaCredentials("user-1", "facebook", "old-encrypted");
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(existing));
        when(encryptionService.encrypt("new-token")).thenReturn("new-encrypted");
        when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));

        SocialMediaCredentials saved = service.saveCredentials("user-1", "facebook", "new-token");

        assertSame(existing, saved);
        assertEquals("new-encrypted", existing.getEncryptedCredentials());
        assertEquals("facebook", existing.getPlatform());
    }

    @Test
    void getCredentialsShouldReturnNullWhenInputIsInvalid() {
        assertNull(service.getCredentials(null, "facebook"));
        assertNull(service.getCredentials("user-1", null));
    }

    @Test
    void getCredentialsShouldReturnNullWhenCredentialsAreMissing() {
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.empty());

        assertNull(service.getCredentials("user-1", "FACEBOOK"));
    }

    @Test
    void getCredentialsShouldDecryptStringCredentials() {
        SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "facebook", "encrypted-token");
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(entity));
        when(encryptionService.decrypt("encrypted-token")).thenReturn("plain-token");

        assertEquals("plain-token", service.getCredentials("user-1", "FACEBOOK"));
    }

    @Test
    void getCredentialsShouldDecryptNonStringCredentialsViaToString() {
        SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "telegram", new StringBuilder("encrypted-json"));
        when(repository.findByUserIdAndPlatform("user-1", "telegram")).thenReturn(Optional.of(entity));
        when(encryptionService.decrypt("encrypted-json")).thenReturn("{\"botToken\":\"t\"}");

        assertEquals("{\"botToken\":\"t\"}", service.getCredentials("user-1", "telegram"));
    }

    @Test
    void getCredentialsShouldWrapDecryptionFailures() {
        SocialMediaCredentials entity = new SocialMediaCredentials("user-1", "facebook", "encrypted-token");
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(entity));
        when(encryptionService.decrypt("encrypted-token")).thenThrow(new RuntimeException("decrypt failed"));

        RuntimeException exception = assertThrows(
                RuntimeException.class,
                () -> service.getCredentials("user-1", "facebook"));

        assertEquals("Failed to decrypt credentials", exception.getMessage());
        assertNotNull(exception.getCause());
    }

    @Test
    void hasCredentialsShouldReturnFalseForInvalidInput() {
        assertTrue(!service.hasCredentials(null, "facebook"));
        assertTrue(!service.hasCredentials("user-1", null));
    }

    @Test
    void hasCredentialsShouldCheckRepositoryUsingLowercasePlatform() {
        when(repository.findByUserIdAndPlatform("user-1", "facebook"))
                .thenReturn(Optional.of(new SocialMediaCredentials("user-1", "facebook", "encrypted")));

        assertTrue(service.hasCredentials("user-1", "FACEBOOK"));
    }

    @Test
    void deleteCredentialsShouldThrowForInvalidInput() {
        assertThrows(IllegalArgumentException.class, () -> service.deleteCredentials(null, "facebook"));
        assertThrows(IllegalArgumentException.class, () -> service.deleteCredentials("user-1", null));
    }

    @Test
    void deleteCredentialsShouldUseLowercasePlatform() {
        service.deleteCredentials("user-1", "FACEBOOK");

        verify(repository).deleteByUserIdAndPlatform("user-1", "facebook");
    }

    @Test
    void getUserPlatformsShouldReturnEmptyListForNullUserId() {
        assertEquals(List.of(), service.getUserPlatforms(null));
        verify(repository, never()).findByUserId(any());
    }

    @Test
    void getUserPlatformsShouldReturnAllPlatforms() {
        when(repository.findByUserId("user-1")).thenReturn(List.of(
                new SocialMediaCredentials("user-1", "facebook", "e1"),
                new SocialMediaCredentials("user-1", "telegram", "e2")));

        assertEquals(List.of("facebook", "telegram"), service.getUserPlatforms("user-1"));
    }

    @Test
    void getUserCredentialsShouldReturnEmptyListForNullUserId() {
        assertEquals(List.of(), service.getUserCredentials(null));
        verify(repository, never()).findByUserId(any());
    }

    @Test
    void getUserCredentialsShouldDelegateToRepository() {
        List<SocialMediaCredentials> credentials = List.of(new SocialMediaCredentials("user-1", "facebook", "encrypted"));
        when(repository.findByUserId("user-1")).thenReturn(credentials);

        assertSame(credentials, service.getUserCredentials("user-1"));
    }

    @Test
    void updateAdAccountIdShouldThrowForInvalidInput() {
        assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId(null, "facebook", "act-1"));
        assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId("user-1", null, "act-1"));
        assertThrows(IllegalArgumentException.class, () -> service.updateAdAccountId("user-1", "facebook", null));
    }

    @Test
    void updateAdAccountIdShouldThrowWhenCredentialsAreMissing() {
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.empty());

        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> service.updateAdAccountId("user-1", "FACEBOOK", "act-1"));

        assertEquals("Credentials not found for user user-1 and platform FACEBOOK", exception.getMessage());
    }

    @Test
    void updateAdAccountIdShouldUpdateAndSaveCredentials() {
        SocialMediaCredentials credentials = new SocialMediaCredentials("user-1", "facebook", "encrypted");
        when(repository.findByUserIdAndPlatform("user-1", "facebook")).thenReturn(Optional.of(credentials));
        when(repository.save(any(SocialMediaCredentials.class))).thenAnswer(invocation -> invocation.getArgument(0));

        service.updateAdAccountId("user-1", "FACEBOOK", "act-123");

        assertEquals("act-123", credentials.getAdAccountId());
        verify(repository).save(credentials);
    }
}
