package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.GameData;
import com.pandoli365.bibimbap.data.SearchCriteria;
import com.pandoli365.bibimbap.data.TagData;
import com.pandoli365.bibimbap.mapper.GamesMapper;
import com.pandoli365.bibimbap.mapper.TagsMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SearchControllerTest {

    @Mock
    private GamesMapper gamesMapper;

    @Mock
    private TagsMapper tagsMapper;

    @Test
    void searchReturnsIndexViewWithDefaults() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        String view = controller.search(null, null, null, "and", "latest", null, model);

        assertThat(view).isEqualTo("index");
        assertThat(model.getAttribute("games")).isNotNull();
        assertThat(model.getAttribute("searchSort")).isEqualTo("latest");
        assertThat(model.getAttribute("searchTagMode")).isEqualTo("and");
    }

    @Test
    void searchFallsBackToLatestForUnknownSort() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "and", "garbage", null, model);

        assertThat(capture().getSort()).isEqualTo("latest");
        assertThat(model.getAttribute("searchSort")).isEqualTo("latest");
    }

    @Test
    void searchPassesWhitelistedSort() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "and", "views", null, model);

        assertThat(capture().getSort()).isEqualTo("views");
    }

    @Test
    void searchFallsBackToAndForUnknownTagMode() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "xxx", "latest", null, model);

        assertThat(capture().getTagMode()).isEqualTo("and");
    }

    @Test
    void searchParsesTagsAndDropsInvalidSlugs() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        // "rpg","horror" 통과, "!" → toSlug "" → isAllowed false → 제외.
        controller.search(null, null, "rpg, horror ,!", "and", "latest", null, model);

        SearchCriteria criteria = capture();
        assertThat(criteria.getTags()).containsExactly("rpg", "horror");
        assertThat(criteria.getTagCount()).isEqualTo(2);
    }

    @Test
    void searchSetsTagsNullWhenNoTags() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "and", "latest", null, model);

        SearchCriteria criteria = capture();
        assertThat(criteria.getTags()).isNull();
        assertThat(criteria.getTagCount()).isEqualTo(0);
    }

    @Test
    void searchTrimsBlankKeywordToNull() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search("   ", null, null, "and", "latest", null, model);

        assertThat(capture().getKeyword()).isNull();
    }

    @Test
    void searchPassesKeywordThrough() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search("rpg", null, null, "and", "latest", null, model);

        assertThat(capture().getKeyword()).isEqualTo("rpg");
    }

    @Test
    void searchPassesJamSlug() {
        SearchController controller = controller();
        stubEmptyResults();
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "and", "latest", "ludum-dare", model);

        assertThat(capture().getJamSlug()).isEqualTo("ludum-dare");
    }

    @Test
    void searchLoadsAvailableTagsIntoModel() {
        SearchController controller = controller();
        List<TagData> tags = List.of(tagData(1L, "RPG", "rpg"));
        when(gamesMapper.searchGamesAdvanced(any(SearchCriteria.class))).thenReturn(List.<GameData>of());
        when(tagsMapper.listActiveByType("GAME", null)).thenReturn(tags);
        Model model = new ExtendedModelMap();

        controller.search(null, null, null, "and", "latest", null, model);

        assertThat(model.getAttribute("availableTags")).isEqualTo(tags);
    }

    // ---- helpers ----

    private SearchController controller() {
        return new SearchController(gamesMapper, tagsMapper);
    }

    private void stubEmptyResults() {
        when(gamesMapper.searchGamesAdvanced(any(SearchCriteria.class))).thenReturn(List.<GameData>of());
        when(tagsMapper.listActiveByType("GAME", null)).thenReturn(List.<TagData>of());
    }

    private SearchCriteria capture() {
        ArgumentCaptor<SearchCriteria> captor = ArgumentCaptor.forClass(SearchCriteria.class);
        verify(gamesMapper).searchGamesAdvanced(captor.capture());
        return captor.getValue();
    }

    private TagData tagData(long id, String name, String slug) {
        TagData t = new TagData();
        t.setId(id);
        t.setName(name);
        t.setSlug(slug);
        return t;
    }
}
