package com.pandoli365.bibimbap.service;

import com.pandoli365.bibimbap.data.UnityFeedItemData;
import com.pandoli365.bibimbap.data.UnityFeedSourceData;
import com.pandoli365.bibimbap.mapper.UnityFeedItemsMapper;
import com.pandoli365.bibimbap.mapper.UnityFeedSourcesMapper;
import com.pandoli365.bibimbap.security.SsrfSafeFetcher;
import com.pandoli365.bibimbap.security.SsrfSafeFetcher.FetchResult;
import com.pandoli365.bibimbap.service.FeedParser.FeedItem;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.net.URI;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UnityFeedPollerTest {

    private static final long SOURCE_ID = 7L;
    private static final String FEED_URL = "https://unity.com/feed.xml";

    @Mock
    private SsrfSafeFetcher ssrfSafeFetcher;
    @Mock
    private FeedParser feedParser;
    @Mock
    private UnityFeedSourcesMapper sourcesMapper;
    @Mock
    private UnityFeedItemsMapper itemsMapper;

    private UnityFeedPoller poller;

    @BeforeEach
    void setUp() {
        poller = new UnityFeedPoller(ssrfSafeFetcher, feedParser, sourcesMapper, itemsMapper);
    }

    private UnityFeedSourceData source() {
        UnityFeedSourceData source = new UnityFeedSourceData();
        source.setId(SOURCE_ID);
        source.setName("Unity");
        source.setFeedUrl(FEED_URL);
        source.setIsActive(true);
        return source;
    }

    private Optional<FetchResult> okFetch() {
        return Optional.of(new FetchResult(
                200,
                "application/rss+xml",
                new byte[]{1, 2, 3},
                URI.create(FEED_URL)));
    }

    private FeedItem item(String guid) {
        return new FeedItem(guid, "title-" + guid, "https://unity.com/" + guid, null);
    }

    @Test
    void insertsNewGuids() {
        when(sourcesMapper.getById(SOURCE_ID)).thenReturn(source());
        when(ssrfSafeFetcher.fetch(any(URI.class), anyLong(), any())).thenReturn(okFetch());
        when(feedParser.parse(any())).thenReturn(List.of(item("A"), item("B")));
        when(itemsMapper.existsByGuid(eq(SOURCE_ID), anyString())).thenReturn(false);
        when(itemsMapper.insertIgnoreDup(any(UnityFeedItemData.class))).thenReturn(1);

        int newCount = poller.pollOnce(SOURCE_ID);

        assertEquals(2, newCount);
        verify(itemsMapper, times(2)).insertIgnoreDup(any(UnityFeedItemData.class));
        verify(sourcesMapper).updateCursor(SOURCE_ID, "A");
    }

    @Test
    void dedupesAllExistingGuids() {
        when(sourcesMapper.getById(SOURCE_ID)).thenReturn(source());
        when(ssrfSafeFetcher.fetch(any(URI.class), anyLong(), any())).thenReturn(okFetch());
        when(feedParser.parse(any())).thenReturn(List.of(item("A"), item("B")));
        when(itemsMapper.existsByGuid(eq(SOURCE_ID), anyString())).thenReturn(true);

        int newCount = poller.pollOnce(SOURCE_ID);

        assertEquals(0, newCount);
        verify(itemsMapper, never()).insertIgnoreDup(any(UnityFeedItemData.class));
        verify(sourcesMapper).updateCursor(SOURCE_ID, "A");
    }

    @Test
    void insertsOnlyNewGuidInMixedBatch() {
        when(sourcesMapper.getById(SOURCE_ID)).thenReturn(source());
        when(ssrfSafeFetcher.fetch(any(URI.class), anyLong(), any())).thenReturn(okFetch());
        when(feedParser.parse(any())).thenReturn(List.of(item("A"), item("B")));
        when(itemsMapper.existsByGuid(SOURCE_ID, "A")).thenReturn(true);
        when(itemsMapper.existsByGuid(SOURCE_ID, "B")).thenReturn(false);
        when(itemsMapper.insertIgnoreDup(any(UnityFeedItemData.class))).thenReturn(1);

        int newCount = poller.pollOnce(SOURCE_ID);

        assertEquals(1, newCount);
        verify(itemsMapper, times(1)).insertIgnoreDup(any(UnityFeedItemData.class));
        verify(sourcesMapper).updateCursor(SOURCE_ID, "A");
    }

    @Test
    void recordsErrorWhenFetchFails() {
        when(sourcesMapper.getById(SOURCE_ID)).thenReturn(source());
        when(ssrfSafeFetcher.fetch(any(URI.class), anyLong(), any())).thenReturn(Optional.empty());

        int newCount = poller.pollOnce(SOURCE_ID);

        assertEquals(0, newCount);
        verify(sourcesMapper).updateError(eq(SOURCE_ID), anyString());
        verify(itemsMapper, never()).insertIgnoreDup(any(UnityFeedItemData.class));
    }
}
