package com.storycove.service;

import com.storycove.dto.EPUBImportRequest;
import com.storycove.dto.EPUBImportResponse;
import com.storycove.entity.*;
import com.storycove.repository.ReadingPositionRepository;
import com.storycove.service.exception.InvalidFileException;
import com.storycove.service.exception.ResourceNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Tests for EPUBImportService.
 * Note: These tests mock the EPUB parsing since nl.siegmann.epublib is complex to test.
 * Integration tests should be added separately to test actual EPUB file parsing.
 */
@ExtendWith(MockitoExtension.class)
class EPUBImportServiceTest {

    @Mock
    private StoryService storyService;

    @Mock
    private AuthorService authorService;

    @Mock
    private SeriesService seriesService;

    @Mock
    private TagService tagService;

    @Mock
    private ReadingPositionRepository readingPositionRepository;

    @Mock
    private HtmlSanitizationService sanitizationService;

    @Mock
    private ImageService imageService;

    @Mock
    private LibraryService libraryService;

    @InjectMocks
    private EPUBImportService epubImportService;

    private EPUBImportRequest testRequest;
    private Story testStory;
    private Author testAuthor;
    private Series testSeries;
    private UUID storyId;

    @BeforeEach
    void setUp() {
        storyId = UUID.randomUUID();

        testStory = new Story();
        testStory.setId(storyId);
        testStory.setTitle("Test Story");
        testStory.setWordCount(1000);

        testAuthor = new Author();
        testAuthor.setId(UUID.randomUUID());
        testAuthor.setName("Test Author");

        testSeries = new Series();
        testSeries.setId(UUID.randomUUID());
        testSeries.setName("Test Series");

        testRequest = new EPUBImportRequest();
    }

    // ========================================
    // File Validation Tests
    // ========================================

    @Test
    @DisplayName("Should reject null EPUB file")
    void testNullEPUBFile() {
        // Arrange
        testRequest.setEpubFile(null);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("EPUB file is required", response.getMessage());
    }

    @Test
    @DisplayName("Should reject empty EPUB file")
    void testEmptyEPUBFile() {
        // Arrange
        MockMultipartFile emptyFile = new MockMultipartFile(
            "file", "test.epub", "application/epub+zip", new byte[0]
        );
        testRequest.setEpubFile(emptyFile);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("EPUB file is required", response.getMessage());
    }

    @Test
    @DisplayName("Should reject non-EPUB file by extension")
    void testInvalidFileExtension() {
        // Arrange
        MockMultipartFile pdfFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", "fake content".getBytes()
        );
        testRequest.setEpubFile(pdfFile);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Invalid EPUB file format", response.getMessage());
    }

    @Test
    @DisplayName("Should validate EPUB file and return errors")
    void testValidateEPUBFile() {
        // Arrange
        MockMultipartFile invalidFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", "fake content".getBytes()
        );

        // Act
        List<String> errors = epubImportService.validateEPUBFile(invalidFile);

        // Assert
        assertNotNull(errors);
        assertFalse(errors.isEmpty());
        assertTrue(errors.stream().anyMatch(e -> e.contains("Invalid EPUB file format")));
    }

    @Test
    @DisplayName("Should validate file size limit")
    void testFileSizeLimit() {
        // Arrange
        byte[] largeData = new byte[101 * 1024 * 1024]; // 101MB
        MockMultipartFile largeFile = new MockMultipartFile(
            "file", "large.epub", "application/epub+zip", largeData
        );

        // Act
        List<String> errors = epubImportService.validateEPUBFile(largeFile);

        // Assert
        assertTrue(errors.stream().anyMatch(e -> e.contains("100MB limit")));
    }

    @Test
    @DisplayName("Should accept valid EPUB with correct extension")
    void testAcceptValidEPUBExtension() {
        // Arrange
        MockMultipartFile validFile = new MockMultipartFile(
            "file", "test.epub", "application/epub+zip", createMinimalEPUB()
        );
        testRequest.setEpubFile(validFile);

        // Note: This will fail at parsing since we don't have a real EPUB
        // But it should pass the extension validation
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert - should fail at parsing, not at validation
        assertFalse(response.isSuccess());
        assertNotEquals("Invalid EPUB file format", response.getMessage());
    }

    @Test
    @DisplayName("Should accept EPUB with application/zip content type")
    void testAcceptZipContentType() {
        // Arrange
        MockMultipartFile zipFile = new MockMultipartFile(
            "file", "test.epub", "application/zip", createMinimalEPUB()
        );
        testRequest.setEpubFile(zipFile);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert - should not fail at content type validation
        assertFalse(response.isSuccess());
        assertNotEquals("Invalid EPUB file format", response.getMessage());
    }

    // ========================================
    // Request Parameter Tests
    // ========================================

    @Test
    @DisplayName("Should handle createMissingAuthor flag")
    void testCreateMissingAuthor() {
        // This is an integration-level test and would require actual EPUB parsing
        // We verify the flag is present in the request object
        testRequest.setCreateMissingAuthor(true);
        assertTrue(testRequest.getCreateMissingAuthor());
    }

    @Test
    @DisplayName("Should handle createMissingSeries flag")
    void testCreateMissingSeries() {
        testRequest.setCreateMissingSeries(true);
        testRequest.setSeriesName("New Series");
        testRequest.setSeriesVolume(1.0);

        assertTrue(testRequest.getCreateMissingSeries());
        assertEquals("New Series", testRequest.getSeriesName());
        assertEquals(1, testRequest.getSeriesVolume());
    }

    @Test
    @DisplayName("Should handle extractCover flag")
    void testExtractCoverFlag() {
        testRequest.setExtractCover(true);
        assertTrue(testRequest.getExtractCover());

        testRequest.setExtractCover(false);
        assertFalse(testRequest.getExtractCover());
    }

    @Test
    @DisplayName("Should handle preserveReadingPosition flag")
    void testPreserveReadingPositionFlag() {
        testRequest.setPreserveReadingPosition(true);
        assertTrue(testRequest.getPreserveReadingPosition());
    }

    @Test
    @DisplayName("Should handle custom tags")
    void testCustomTags() {
        List<String> tags = Arrays.asList("fantasy", "adventure", "magic");
        testRequest.setTags(tags);

        assertEquals(3, testRequest.getTags().size());
        assertTrue(testRequest.getTags().contains("fantasy"));
    }

    // ========================================
    // Author Handling Tests
    // ========================================

    @Test
    @DisplayName("Should use provided authorId when available")
    void testUseProvidedAuthorId() {
        // This would require mocking the EPUB parsing
        // We verify the request accepts authorId
        UUID authorId = UUID.randomUUID();
        testRequest.setAuthorId(authorId);
        assertEquals(authorId, testRequest.getAuthorId());
    }

    @Test
    @DisplayName("Should use provided authorName")
    void testUseProvidedAuthorName() {
        testRequest.setAuthorName("Custom Author Name");
        assertEquals("Custom Author Name", testRequest.getAuthorName());
    }

    // ========================================
    // Series Handling Tests
    // ========================================

    @Test
    @DisplayName("Should use provided seriesId and volume")
    void testUseProvidedSeriesId() {
        UUID seriesId = UUID.randomUUID();
        testRequest.setSeriesId(seriesId);
        testRequest.setSeriesVolume(5.0);

        assertEquals(seriesId, testRequest.getSeriesId());
        assertEquals(5, testRequest.getSeriesVolume());
    }

    // ========================================
    // Error Handling Tests
    // ========================================

    @Test
    @DisplayName("Should handle corrupt EPUB file gracefully")
    void testCorruptEPUBFile() {
        // Arrange
        MockMultipartFile corruptFile = new MockMultipartFile(
            "file", "corrupt.epub", "application/epub+zip", "not a real epub".getBytes()
        );
        testRequest.setEpubFile(corruptFile);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertNotNull(response.getMessage());
        assertTrue(response.getMessage().contains("Failed to import EPUB"));
    }

    @Test
    @DisplayName("Should handle missing metadata gracefully")
    void testMissingMetadata() {
        // Arrange
        MockMultipartFile epubFile = new MockMultipartFile(
            "file", "test.epub", "application/epub+zip", createMinimalEPUB()
        );

        // Act
        List<String> errors = epubImportService.validateEPUBFile(epubFile);

        // Assert - validation should catch missing metadata
        assertNotNull(errors);
    }

    // ========================================
    // Response Tests
    // ========================================

    @Test
    @DisplayName("Should create success response with correct fields")
    void testSuccessResponse() {
        // Arrange
        EPUBImportResponse response = EPUBImportResponse.success(storyId, "Test Story");
        response.setWordCount(1500);
        response.setTotalChapters(10);

        // Assert
        assertTrue(response.isSuccess());
        assertEquals(storyId, response.getStoryId());
        assertEquals("Test Story", response.getStoryTitle());
        assertEquals(1500, response.getWordCount());
        assertEquals(10, response.getTotalChapters());
        assertNull(response.getMessage());
    }

    @Test
    @DisplayName("Should create error response with message")
    void testErrorResponse() {
        // Arrange
        EPUBImportResponse response = EPUBImportResponse.error("Test error message");

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Test error message", response.getMessage());
        assertNull(response.getStoryId());
        assertNull(response.getStoryTitle());
    }

    // ========================================
    // Integration Scenario Tests
    // ========================================

    @Test
    @DisplayName("Should handle complete import workflow (mock)")
    void testCompleteImportWorkflow() {
        // This test verifies that all the request parameters are properly structured
        // Actual EPUB parsing would be tested in integration tests

        // Arrange - Create a complete request
        testRequest.setEpubFile(new MockMultipartFile(
            "file", "story.epub", "application/epub+zip", createMinimalEPUB()
        ));
        testRequest.setAuthorName("Jane Doe");
        testRequest.setCreateMissingAuthor(true);
        testRequest.setSeriesName("Epic Series");
        testRequest.setSeriesVolume(3.0);
        testRequest.setCreateMissingSeries(true);
        testRequest.setTags(Arrays.asList("fantasy", "adventure"));
        testRequest.setExtractCover(true);
        testRequest.setPreserveReadingPosition(true);

        // Assert - All parameters set correctly
        assertNotNull(testRequest.getEpubFile());
        assertEquals("Jane Doe", testRequest.getAuthorName());
        assertTrue(testRequest.getCreateMissingAuthor());
        assertEquals("Epic Series", testRequest.getSeriesName());
        assertEquals(3, testRequest.getSeriesVolume());
        assertTrue(testRequest.getCreateMissingSeries());
        assertEquals(2, testRequest.getTags().size());
        assertTrue(testRequest.getExtractCover());
        assertTrue(testRequest.getPreserveReadingPosition());
    }

    @Test
    @DisplayName("Should handle minimal import request")
    void testMinimalImportRequest() {
        // Arrange - Only required field
        testRequest.setEpubFile(new MockMultipartFile(
            "file", "simple.epub", "application/epub+zip", createMinimalEPUB()
        ));

        // Assert - Optional fields are null/false
        assertNotNull(testRequest.getEpubFile());
        assertNull(testRequest.getAuthorId());
        assertNull(testRequest.getAuthorName());
        assertNull(testRequest.getSeriesId());
        assertNull(testRequest.getTags());
    }

    // ========================================
    // Edge Cases
    // ========================================

    @Test
    @DisplayName("Should handle EPUB with special characters in filename")
    void testSpecialCharactersInFilename() {
        // Arrange
        MockMultipartFile fileWithSpecialChars = new MockMultipartFile(
            "file", "test story (2024) #1.epub", "application/epub+zip", createMinimalEPUB()
        );
        testRequest.setEpubFile(fileWithSpecialChars);

        // Act
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert - should not fail due to filename
        assertNotNull(response);
    }

    @Test
    @DisplayName("Should handle EPUB with null content type")
    void testNullContentType() {
        // Arrange
        MockMultipartFile fileWithNullContentType = new MockMultipartFile(
            "file", "test.epub", null, createMinimalEPUB()
        );
        testRequest.setEpubFile(fileWithNullContentType);

        // Act - Should still validate based on extension
        EPUBImportResponse response = epubImportService.importEPUB(testRequest);

        // Assert - should not fail at validation, only at parsing
        assertNotNull(response);
    }

    @Test
    @DisplayName("Should trim whitespace from author name")
    void testTrimAuthorName() {
        testRequest.setAuthorName("  John Doe  ");
        // The service should trim this internally
        assertEquals("  John Doe  ", testRequest.getAuthorName());
    }

    @Test
    @DisplayName("Should handle empty tags list")
    void testEmptyTagsList() {
        testRequest.setTags(new ArrayList<>());
        assertNotNull(testRequest.getTags());
        assertTrue(testRequest.getTags().isEmpty());
    }

    @Test
    @DisplayName("Should handle duplicate tags in request")
    void testDuplicateTags() {
        List<String> tagsWithDuplicates = Arrays.asList("fantasy", "adventure", "fantasy");
        testRequest.setTags(tagsWithDuplicates);

        assertEquals(3, testRequest.getTags().size());
        // The service should handle deduplication internally
    }

    // ========================================
    // Helper Methods
    // ========================================

    /**
     * Creates minimal EPUB-like content for testing.
     * Note: This is not a real EPUB, just test data.
     */
    private byte[] createMinimalEPUB() {
        // This creates minimal test data that looks like an EPUB structure
        // Real EPUB parsing would require a proper EPUB file structure
        return "PK\u0003\u0004fake epub content".getBytes();
    }
}
