package com.storycove.service;

import com.storycove.dto.FileImportResponse;
import com.storycove.dto.PDFImportRequest;
import com.storycove.entity.*;
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 java.util.*;

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

/**
 * Tests for PDFImportService.
 * Note: These tests mock the PDF parsing since Apache PDFBox is complex to test.
 * Integration tests should be added separately to test actual PDF file parsing.
 */
@ExtendWith(MockitoExtension.class)
class PDFImportServiceTest {

    @Mock
    private StoryService storyService;

    @Mock
    private AuthorService authorService;

    @Mock
    private SeriesService seriesService;

    @Mock
    private TagService tagService;

    @Mock
    private HtmlSanitizationService sanitizationService;

    @Mock
    private ImageService imageService;

    @Mock
    private LibraryService libraryService;

    @InjectMocks
    private PDFImportService pdfImportService;

    private PDFImportRequest 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 PDFImportRequest();
    }

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

    @Test
    @DisplayName("Should reject null PDF file")
    void testNullPDFFile() {
        // Arrange
        testRequest.setPdfFile(null);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("PDF file is required", response.getMessage());
        verify(storyService, never()).create(any(Story.class));
    }

    @Test
    @DisplayName("Should reject empty PDF file")
    void testEmptyPDFFile() {
        // Arrange
        MockMultipartFile emptyFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", new byte[0]
        );
        testRequest.setPdfFile(emptyFile);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("PDF file is required", response.getMessage());
        verify(storyService, never()).create(any(Story.class));
    }

    @Test
    @DisplayName("Should reject non-PDF file by extension")
    void testInvalidFileExtension() {
        // Arrange
        MockMultipartFile invalidFile = new MockMultipartFile(
            "file", "test.txt", "text/plain", "test content".getBytes()
        );
        testRequest.setPdfFile(invalidFile);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("Invalid PDF file format"));
        verify(storyService, never()).create(any(Story.class));
    }

    @Test
    @DisplayName("Should reject file exceeding 300MB size limit")
    void testFileSizeExceedsLimit() {
        // Arrange
        long fileSize = 301L * 1024 * 1024; // 301 MB
        MockMultipartFile largeFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", new byte[(int)Math.min(fileSize, 1000)]
        ) {
            @Override
            public long getSize() {
                return fileSize;
            }
        };
        testRequest.setPdfFile(largeFile);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("Invalid PDF file format"));
        verify(storyService, never()).create(any(Story.class));
    }

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

    @Test
    @DisplayName("Should require author name when not in metadata")
    void testRequiresAuthorName() {
        // Arrange - Create a minimal valid PDF (will fail parsing but test validation)
        MockMultipartFile pdfFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf",
            "%PDF-1.4\n%%EOF".getBytes()
        );
        testRequest.setPdfFile(pdfFile);
        testRequest.setAuthorName(null);
        testRequest.setAuthorId(null);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert
        assertFalse(response.isSuccess());
        // Should fail during import because author is required
        verify(storyService, never()).create(any(Story.class));
    }

    // ========================================
    // Validation Method Tests
    // ========================================

    @Test
    @DisplayName("Should validate PDF file successfully")
    void testValidatePDFFile_Valid() {
        // Arrange
        MockMultipartFile pdfFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf",
            new byte[100]
        );

        // Act
        List<String> errors = pdfImportService.validatePDFFile(pdfFile);

        // Assert - Will have errors because it's not a real PDF, but tests the method exists
        assertNotNull(errors);
    }

    @Test
    @DisplayName("Should return errors for null file in validation")
    void testValidatePDFFile_Null() {
        // Act
        List<String> errors = pdfImportService.validatePDFFile(null);

        // Assert
        assertNotNull(errors);
        assertFalse(errors.isEmpty());
        assertTrue(errors.get(0).contains("required"));
    }

    @Test
    @DisplayName("Should return errors for empty file in validation")
    void testValidatePDFFile_Empty() {
        // Arrange
        MockMultipartFile emptyFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", new byte[0]
        );

        // Act
        List<String> errors = pdfImportService.validatePDFFile(emptyFile);

        // Assert
        assertNotNull(errors);
        assertFalse(errors.isEmpty());
        assertTrue(errors.get(0).contains("required"));
    }

    @Test
    @DisplayName("Should return errors for oversized file in validation")
    void testValidatePDFFile_Oversized() {
        // Arrange
        long fileSize = 301L * 1024 * 1024; // 301 MB
        MockMultipartFile largeFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf", new byte[1000]
        ) {
            @Override
            public long getSize() {
                return fileSize;
            }
        };

        // Act
        List<String> errors = pdfImportService.validatePDFFile(largeFile);

        // Assert
        assertNotNull(errors);
        assertFalse(errors.isEmpty());
        assertTrue(errors.stream().anyMatch(e -> e.contains("300MB")));
    }

    // ========================================
    // Integration Tests (Mocked)
    // ========================================

    @Test
    @DisplayName("Should handle extraction images flag")
    void testExtractImagesFlag() {
        // Arrange
        MockMultipartFile pdfFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf",
            "%PDF-1.4\n%%EOF".getBytes()
        );
        testRequest.setPdfFile(pdfFile);
        testRequest.setAuthorName("Test Author");
        testRequest.setExtractImages(false);

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert - Will fail parsing but tests that the flag is accepted
        assertNotNull(response);
    }

    @Test
    @DisplayName("Should accept tags in request")
    void testAcceptTags() {
        // Arrange
        MockMultipartFile pdfFile = new MockMultipartFile(
            "file", "test.pdf", "application/pdf",
            "%PDF-1.4\n%%EOF".getBytes()
        );
        testRequest.setPdfFile(pdfFile);
        testRequest.setAuthorName("Test Author");
        testRequest.setTags(Arrays.asList("tag1", "tag2"));

        // Act
        FileImportResponse response = pdfImportService.importPDF(testRequest);

        // Assert - Will fail parsing but tests that tags are accepted
        assertNotNull(response);
    }
}
