package com.storycove.repository;

import com.storycove.entity.Author;
import com.storycove.entity.Series;
import com.storycove.entity.Story;
import com.storycove.entity.Tag;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

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

@DisplayName("Story Repository Integration Tests")
class StoryRepositoryTest extends BaseRepositoryTest {

    @Autowired
    private StoryRepository storyRepository;

    @Autowired
    private AuthorRepository authorRepository;

    @Autowired
    private TagRepository tagRepository;

    @Autowired
    private SeriesRepository seriesRepository;

    private Author author;
    private Series series;
    private Tag tag1;
    private Tag tag2;
    private Story story1;
    private Story story2;
    private Story story3;

    @BeforeEach
    void setUp() {
        storyRepository.deleteAll();
        authorRepository.deleteAll();
        tagRepository.deleteAll();
        seriesRepository.deleteAll();

        author = new Author("Test Author");
        authorRepository.save(author);

        series = new Series("Test Series");
        seriesRepository.save(series);

        tag1 = new Tag("fantasy");
        tag2 = new Tag("adventure");
        tagRepository.saveAll(List.of(tag1, tag2));

        story1 = new Story("The Great Adventure");
        story1.setDescription("An epic adventure story");
        story1.setContentHtml("<p>This is the content of the story with many words to test word count.</p>");
        story1.setAuthor(author);
        story1.addTag(tag1);
        story1.addTag(tag2);
        story1.setSourceUrl("https://example.com/story1");

        story2 = new Story("The Sequel");
        story2.setDescription("The sequel to the great adventure");
        story2.setAuthor(author);
        story2.setSeries(series);
        story2.setVolume(1.0);
        story2.addTag(tag1);

        story3 = new Story("The Final Chapter");
        story3.setDescription("The final chapter");
        story3.setAuthor(author);
        story3.setSeries(series);
        story3.setVolume(2.0);

        storyRepository.saveAll(List.of(story1, story2, story3));
    }

    @Test
    @DisplayName("Should find story by title")
    void shouldFindStoryByTitle() {
        Optional<Story> found = storyRepository.findByTitle("The Great Adventure");
        assertTrue(found.isPresent());
        assertEquals("The Great Adventure", found.get().getTitle());
    }

    @Test
    @DisplayName("Should find stories by title containing ignoring case")
    void shouldFindStoriesByTitleContainingIgnoreCase() {
        List<Story> stories = storyRepository.findByTitleContainingIgnoreCase("great");
        assertEquals(1, stories.size());
        assertEquals("The Great Adventure", stories.get(0).getTitle());

        stories = storyRepository.findByTitleContainingIgnoreCase("the");
        assertEquals(3, stories.size());
    }

    @Test
    @DisplayName("Should find stories by author")
    void shouldFindStoriesByAuthor() {
        List<Story> stories = storyRepository.findByAuthor(author);
        assertEquals(3, stories.size());

        Page<Story> page = storyRepository.findByAuthor(author, PageRequest.of(0, 2));
        assertEquals(2, page.getContent().size());
        assertEquals(3, page.getTotalElements());
    }

    @Test
    @DisplayName("Should find stories by series")
    void shouldFindStoriesBySeries() {
        List<Story> stories = storyRepository.findBySeries(series);
        assertEquals(2, stories.size());

        List<Story> orderedStories = storyRepository.findBySeriesOrderByVolume(series.getId());
        assertEquals(2, orderedStories.size());
        assertEquals("The Sequel", orderedStories.get(0).getTitle()); // Part 1
        assertEquals("The Final Chapter", orderedStories.get(1).getTitle()); // Part 2
    }

    @Test
    @DisplayName("Should find story by series and volume")
    void shouldFindStoryBySeriesAndVolume() {
        Optional<Story> found = storyRepository.findBySeriesAndVolume(series.getId(), 1.0);
        assertTrue(found.isPresent());
        assertEquals("The Sequel", found.get().getTitle());

        found = storyRepository.findBySeriesAndVolume(series.getId(), 99.0);
        assertFalse(found.isPresent());
    }


    @Test
    @DisplayName("Should find stories by tag")
    void shouldFindStoriesByTag() {
        List<Story> fantasyStories = storyRepository.findByTag(tag1);
        assertEquals(2, fantasyStories.size());

        List<Story> adventureStories = storyRepository.findByTag(tag2);
        assertEquals(1, adventureStories.size());
        assertEquals("The Great Adventure", adventureStories.get(0).getTitle());
    }

    @Test
    @DisplayName("Should find stories by tag names")
    void shouldFindStoriesByTagNames() {
        List<Story> stories = storyRepository.findByTagNames(List.of("fantasy"));
        assertEquals(2, stories.size());

        stories = storyRepository.findByTagNames(List.of("fantasy", "adventure"));
        assertEquals(2, stories.size()); // Distinct stories with either tag

        Page<Story> page = storyRepository.findByTagNames(List.of("fantasy"), PageRequest.of(0, 1));
        assertEquals(1, page.getContent().size());
        assertEquals(2, page.getTotalElements());
    }

    @Test
    @DisplayName("Should find stories by minimum rating")
    void shouldFindStoriesByMinimumRating() {
        story1.setRating(4);
        story2.setRating(5);
        story3.setRating(4);
        storyRepository.saveAll(List.of(story1, story2, story3));

        List<Story> stories = storyRepository.findByMinimumRating(Integer.valueOf(5));
        assertEquals(1, stories.size());
        assertEquals("The Sequel", stories.get(0).getTitle()); // Rating 5
    }

    @Test
    @DisplayName("Should find top rated stories")
    void shouldFindTopRatedStories() {
        story1.setRating(4);
        story2.setRating(5);
        story3.setRating(4);
        storyRepository.saveAll(List.of(story1, story2, story3));

        List<Story> topRated = storyRepository.findTopRatedStories();
        assertEquals(3, topRated.size());
        assertEquals("The Sequel", topRated.get(0).getTitle());
        assertEquals("The Great Adventure", topRated.get(1).getTitle());
        assertEquals("The Final Chapter", topRated.get(2).getTitle());
    }

    @Test
    @DisplayName("Should find stories by word count range")
    void shouldFindStoriesByWordCountRange() {
        // story1 has content, others don't
        List<Story> stories = storyRepository.findByWordCountRange(1, 100);
        assertEquals(1, stories.size());
        assertEquals("The Great Adventure", stories.get(0).getTitle());

        stories = storyRepository.findByWordCountRange(0, 0);
        assertEquals(2, stories.size()); // story2 and story3 have 0 words
    }




    @Test
    @DisplayName("Should find recently added stories")
    void shouldFindRecentlyAdded() {
        List<Story> recent = storyRepository.findRecentlyAdded();
        assertEquals(3, recent.size());
        // Should be ordered by creation date descending
    }

    @Test
    @DisplayName("Should find stories with source URL")
    void shouldFindStoriesWithSourceUrl() {
        List<Story> withSource = storyRepository.findStoriesWithSourceUrl();
        assertEquals(1, withSource.size());
        assertEquals("The Great Adventure", withSource.get(0).getTitle());
    }

    @Test
    @DisplayName("Should check if story exists by source URL")
    void shouldCheckIfStoryExistsBySourceUrl() {
        assertTrue(storyRepository.existsBySourceUrl("https://example.com/story1"));
        assertFalse(storyRepository.existsBySourceUrl("https://example.com/nonexistent"));
    }

    @Test
    @DisplayName("Should find story by source URL")
    void shouldFindStoryBySourceUrl() {
        Optional<Story> found = storyRepository.findBySourceUrl("https://example.com/story1");
        assertTrue(found.isPresent());
        assertEquals("The Great Adventure", found.get().getTitle());
    }

    @Test
    @DisplayName("Should count stories created since date")
    void shouldCountStoriesCreatedSince() {
        LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
        long count = storyRepository.countStoriesCreatedSince(oneHourAgo);
        assertEquals(3, count); // All stories were created recently
    }

    @Test
    @DisplayName("Should calculate average statistics")
    void shouldCalculateAverageStatistics() {
        Double avgWordCount = storyRepository.findAverageWordCount();
        assertNotNull(avgWordCount);
        assertTrue(avgWordCount >= 0);

        story1.setRating(4);
        story2.setRating(5);
        storyRepository.saveAll(List.of(story1, story2));

        Double avgRating = storyRepository.findOverallAverageRating();
        assertNotNull(avgRating);
        assertEquals(4.5, avgRating, 0.1);

        Long totalWords = storyRepository.findTotalWordCount();
        assertNotNull(totalWords);
        assertTrue(totalWords >= 0);
    }

    @Test
    @DisplayName("Should find stories by keyword")
    void shouldFindStoriesByKeyword() {
        List<Story> stories = storyRepository.findByKeyword("adventure");
        assertEquals(2, stories.size()); // Title and description matches

        stories = storyRepository.findByKeyword("epic");
        assertEquals(1, stories.size());
        assertEquals("The Great Adventure", stories.get(0).getTitle());
    }
}