package com.storycove.entity;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Set;

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

@DisplayName("Story Entity Tests")
class StoryTest {

    private Validator validator;
    private Story story;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        story = new Story("The Great Adventure");
    }

    @Test
    @DisplayName("Should create story with valid title")
    void shouldCreateStoryWithValidTitle() {
        assertEquals("The Great Adventure", story.getTitle());
        assertEquals(0, story.getWordCount());
        assertNull(story.getRating());
        assertNotNull(story.getTags());
        assertTrue(story.getTags().isEmpty());
    }

    @Test
    @DisplayName("Should create story with title and content")
    void shouldCreateStoryWithTitleAndContent() {
        String contentHtml = "<p>This is a test story with some content that has multiple words.</p>";
        Story storyWithContent = new Story("Test Story", contentHtml);
        
        assertEquals("Test Story", storyWithContent.getTitle());
        assertEquals(contentHtml, storyWithContent.getContentHtml());
        assertTrue(storyWithContent.getWordCount() > 0);
    }

    @Test
    @DisplayName("Should fail validation when title is blank")
    void shouldFailValidationWhenTitleIsBlank() {
        story.setTitle("");
        Set<ConstraintViolation<Story>> violations = validator.validate(story);
        assertEquals(1, violations.size());
        assertEquals("Story title is required", violations.iterator().next().getMessage());
    }

    @Test
    @DisplayName("Should fail validation when title is null")
    void shouldFailValidationWhenTitleIsNull() {
        story.setTitle(null);
        Set<ConstraintViolation<Story>> violations = validator.validate(story);
        assertEquals(1, violations.size());
        assertEquals("Story title is required", violations.iterator().next().getMessage());
    }

    @Test
    @DisplayName("Should fail validation when title exceeds 255 characters")
    void shouldFailValidationWhenTitleTooLong() {
        String longTitle = "a".repeat(256);
        story.setTitle(longTitle);
        Set<ConstraintViolation<Story>> violations = validator.validate(story);
        assertEquals(1, violations.size());
        assertEquals("Story title must not exceed 255 characters", violations.iterator().next().getMessage());
    }

    @Test
    @DisplayName("Should fail validation when description exceeds 1000 characters")
    void shouldFailValidationWhenDescriptionTooLong() {
        String longDescription = "a".repeat(1001);
        story.setDescription(longDescription);
        Set<ConstraintViolation<Story>> violations = validator.validate(story);
        assertEquals(1, violations.size());
        assertEquals("Story description must not exceed 1000 characters", violations.iterator().next().getMessage());
    }

    @Test
    @DisplayName("Should update word count when content is set")
    void shouldUpdateWordCountWhenContentIsSet() {
        String htmlContent = "<p>This is a test story with <b>bold</b> text and <i>italic</i> text.</p>";
        story.setContentHtml(htmlContent);
        
        // HTML tags should be stripped for word count and contentPlain is automatically set
        assertTrue(story.getWordCount() > 0);
        assertEquals(11, story.getWordCount()); // "This is a test story with bold text and italic text."
    }


    @Test
    @DisplayName("Should add and remove tags correctly")
    void shouldAddAndRemoveTagsCorrectly() {
        Tag tag1 = new Tag("fantasy");
        Tag tag2 = new Tag("adventure");

        story.addTag(tag1);
        story.addTag(tag2);

        assertEquals(2, story.getTags().size());
        assertTrue(story.getTags().contains(tag1));
        assertTrue(story.getTags().contains(tag2));
        assertTrue(tag1.getStories().contains(story));
        assertTrue(tag2.getStories().contains(story));

        story.removeTag(tag1);
        assertEquals(1, story.getTags().size());
        assertFalse(story.getTags().contains(tag1));
        assertFalse(tag1.getStories().contains(story));
    }

    @Test
    @DisplayName("Should set rating correctly")
    void shouldSetRatingCorrectly() {
        story.setRating(4);
        assertEquals(4, story.getRating());

        story.setRating(5);
        assertEquals(5, story.getRating());

        story.setRating(null);
        assertNull(story.getRating());
    }


    @Test
    @DisplayName("Should check if story is part of series correctly")
    void shouldCheckIfStoryIsPartOfSeriesCorrectly() {
        assertFalse(story.isPartOfSeries());

        Series series = new Series("Test Series");
        story.setSeries(series);
        assertFalse(story.isPartOfSeries()); // Still false because no volume

        story.setVolume(1.0);
        assertTrue(story.isPartOfSeries());

        story.setSeries(null);
        assertFalse(story.isPartOfSeries()); // False because no series
    }

    @Test
    @DisplayName("Should handle equals and hashCode correctly")
    void shouldHandleEqualsAndHashCodeCorrectly() {
        Story story1 = new Story("Story 1");
        Story story2 = new Story("Story 2");
        Story story3 = new Story("Story 1");

        assertNotEquals(story1, story2);
        assertNotEquals(story1, story3); // Different because IDs are null
        assertEquals(story1, story1);
        assertNotEquals(story1, null);
        assertNotEquals(story1, "not a story");

        assertEquals(story1.hashCode(), story1.hashCode());
    }

    @Test
    @DisplayName("Should have proper toString representation")
    void shouldHaveProperToStringRepresentation() {
        String toString = story.toString();
        assertTrue(toString.contains("The Great Adventure"));
        assertTrue(toString.contains("Story{"));
        assertTrue(toString.contains("wordCount=0"));
        assertTrue(toString.contains("rating=null"));
    }

    @Test
    @DisplayName("Should pass validation with maximum allowed lengths")
    void shouldPassValidationWithMaxAllowedLengths() {
        String maxTitle = "a".repeat(255);
        String maxDescription = "a".repeat(1000);
        
        story.setTitle(maxTitle);
        story.setDescription(maxDescription);
        
        Set<ConstraintViolation<Story>> violations = validator.validate(story);
        assertTrue(violations.isEmpty());
    }

    @Test
    @DisplayName("Should handle empty content gracefully")
    void shouldHandleEmptyContentGracefully() {
        story.setContentHtml("");
        // Empty string, when trimmed and split, creates an array with one empty element
        assertEquals(1, story.getWordCount()); 

        // Initialize a new story to test null handling properly  
        Story newStory = new Story("Test");
        // Don't call setContentHtml(null) as it may cause issues with Jsoup.parse(null)
        // Just verify that a new story has 0 word count initially
        assertEquals(0, newStory.getWordCount());
    }

    @Test
    @DisplayName("Should handle HTML content correctly")
    void shouldHandleHtmlContentCorrectly() {
        String htmlContent = "<div><p>Hello <span>world</span>!</p><br/><p>This is a test.</p></div>";
        story.setContentHtml(htmlContent);
        
        // Should count words after stripping HTML: "Hello world! This is a test."
        assertEquals(6, story.getWordCount());
    }
    
    @Test
    @DisplayName("Should prefer contentPlain over contentHtml for word count")
    void shouldPreferContentPlainOverContentHtml() {
        String htmlContent = "<p>HTML content with <b>five words</b></p>";
        
        story.setContentHtml(htmlContent); // This automatically sets contentPlain via Jsoup
        // The HTML will be parsed to: "HTML content with five words" (5 words)
        
        // Should use the contentPlain that was automatically set from HTML
        assertEquals(5, story.getWordCount());
    }
}