package com.storycove.service;

import com.storycove.dto.*;
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 ZIPImportService.
 */
@ExtendWith(MockitoExtension.class)
class ZIPImportServiceTest {

    @Mock
    private EPUBImportService epubImportService;

    @Mock
    private PDFImportService pdfImportService;

    @InjectMocks
    private ZIPImportService zipImportService;

    private ZIPImportRequest testImportRequest;

    @BeforeEach
    void setUp() {
        testImportRequest = new ZIPImportRequest();
    }

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

    @Test
    @DisplayName("Should reject null ZIP file")
    void testNullZIPFile() {
        // Act
        ZIPAnalysisResponse response = zipImportService.analyzeZIPFile(null);

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

    @Test
    @DisplayName("Should reject empty ZIP file")
    void testEmptyZIPFile() {
        // Arrange
        MockMultipartFile emptyFile = new MockMultipartFile(
            "file", "test.zip", "application/zip", new byte[0]
        );

        // Act
        ZIPAnalysisResponse response = zipImportService.analyzeZIPFile(emptyFile);

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

    @Test
    @DisplayName("Should reject non-ZIP file")
    void testInvalidFileType() {
        // Arrange
        MockMultipartFile invalidFile = new MockMultipartFile(
            "file", "test.txt", "text/plain", "test content".getBytes()
        );

        // Act
        ZIPAnalysisResponse response = zipImportService.analyzeZIPFile(invalidFile);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("Invalid ZIP file format"));
    }

    @Test
    @DisplayName("Should reject oversized ZIP file")
    void testOversizedZIPFile() {
        // Arrange
        long fileSize = 1025L * 1024 * 1024; // 1025 MB (> 1GB limit)
        MockMultipartFile largeFile = new MockMultipartFile(
            "file", "test.zip", "application/zip", new byte[1000]
        ) {
            @Override
            public long getSize() {
                return fileSize;
            }
        };

        // Act
        ZIPAnalysisResponse response = zipImportService.analyzeZIPFile(largeFile);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("exceeds"));
        assertTrue(response.getMessage().contains("1024MB") || response.getMessage().contains("1GB"));
    }

    // ========================================
    // Import Request Validation Tests
    // ========================================

    @Test
    @DisplayName("Should reject import with invalid session ID")
    void testInvalidSessionId() {
        // Arrange
        testImportRequest.setZipSessionId("invalid-session-id");
        testImportRequest.setSelectedFiles(Arrays.asList("file1.epub"));

        // Act
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("Invalid") || response.getMessage().contains("expired"));
    }

    @Test
    @DisplayName("Should reject import with no selected files")
    void testNoSelectedFiles() {
        // Arrange
        testImportRequest.setZipSessionId("some-session-id");
        testImportRequest.setSelectedFiles(Collections.emptyList());

        // Act
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("No files selected") || response.getMessage().contains("Invalid"));
    }

    @Test
    @DisplayName("Should reject import with null selected files")
    void testNullSelectedFiles() {
        // Arrange
        testImportRequest.setZipSessionId("some-session-id");
        testImportRequest.setSelectedFiles(null);

        // Act
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("No files selected") || response.getMessage().contains("Invalid"));
    }

    // ========================================
    // ZIP Analysis Tests
    // ========================================

    @Test
    @DisplayName("Should handle corrupted ZIP file gracefully")
    void testCorruptedZIPFile() {
        // Arrange
        MockMultipartFile corruptedFile = new MockMultipartFile(
            "file", "test.zip", "application/zip",
            "PK\3\4corrupted data".getBytes()
        );

        // Act
        ZIPAnalysisResponse response = zipImportService.analyzeZIPFile(corruptedFile);

        // Assert
        assertFalse(response.isSuccess());
        assertNotNull(response.getMessage());
    }

    // ========================================
    // Helper Method Tests
    // ========================================

    @Test
    @DisplayName("Should accept default metadata in import request")
    void testDefaultMetadata() {
        // Arrange
        testImportRequest.setZipSessionId("test-session");
        testImportRequest.setSelectedFiles(Arrays.asList("file1.epub"));
        testImportRequest.setDefaultAuthorName("Default Author");
        testImportRequest.setDefaultTags(Arrays.asList("tag1", "tag2"));

        // Act - will fail due to invalid session, but tests that metadata is accepted
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertNotNull(response);
        assertFalse(response.isSuccess()); // Expected to fail due to invalid session
    }

    @Test
    @DisplayName("Should accept per-file metadata in import request")
    void testPerFileMetadata() {
        // Arrange
        Map<String, ZIPImportRequest.FileImportMetadata> fileMetadata = new HashMap<>();
        ZIPImportRequest.FileImportMetadata metadata = new ZIPImportRequest.FileImportMetadata();
        metadata.setAuthorName("Specific Author");
        metadata.setTags(Arrays.asList("tag1"));
        fileMetadata.put("file1.epub", metadata);

        testImportRequest.setZipSessionId("test-session");
        testImportRequest.setSelectedFiles(Arrays.asList("file1.epub"));
        testImportRequest.setFileMetadata(fileMetadata);

        // Act - will fail due to invalid session, but tests that metadata is accepted
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertNotNull(response);
        assertFalse(response.isSuccess()); // Expected to fail due to invalid session
    }

    @Test
    @DisplayName("Should accept createMissing flags")
    void testCreateMissingFlags() {
        // Arrange
        testImportRequest.setZipSessionId("test-session");
        testImportRequest.setSelectedFiles(Arrays.asList("file1.epub"));
        testImportRequest.setCreateMissingAuthor(false);
        testImportRequest.setCreateMissingSeries(false);
        testImportRequest.setExtractImages(false);

        // Act - will fail due to invalid session, but tests that flags are accepted
        ZIPImportResponse response = zipImportService.importFromZIP(testImportRequest);

        // Assert
        assertNotNull(response);
    }

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

    @Test
    @DisplayName("ZIPImportResponse should calculate statistics correctly")
    void testZIPImportResponseStatistics() {
        // Arrange
        List<FileImportResponse> results = new ArrayList<>();

        FileImportResponse success1 = FileImportResponse.success(UUID.randomUUID(), "Story 1", "EPUB");
        FileImportResponse success2 = FileImportResponse.success(UUID.randomUUID(), "Story 2", "PDF");
        FileImportResponse failure = FileImportResponse.error("Import failed", "story3.epub");

        results.add(success1);
        results.add(success2);
        results.add(failure);

        // Act
        ZIPImportResponse response = ZIPImportResponse.create(results);

        // Assert
        assertNotNull(response);
        assertEquals(3, response.getTotalFiles());
        assertEquals(2, response.getSuccessfulImports());
        assertEquals(1, response.getFailedImports());
        assertTrue(response.isSuccess()); // Partial success
        assertTrue(response.getMessage().contains("2 imported"));
    }

    @Test
    @DisplayName("ZIPImportResponse should handle all failures")
    void testZIPImportResponseAllFailures() {
        // Arrange
        List<FileImportResponse> results = new ArrayList<>();
        results.add(FileImportResponse.error("Error 1", "file1.epub"));
        results.add(FileImportResponse.error("Error 2", "file2.pdf"));

        // Act
        ZIPImportResponse response = ZIPImportResponse.create(results);

        // Assert
        assertNotNull(response);
        assertEquals(2, response.getTotalFiles());
        assertEquals(0, response.getSuccessfulImports());
        assertEquals(2, response.getFailedImports());
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("failed"));
    }

    @Test
    @DisplayName("ZIPImportResponse should handle all successes")
    void testZIPImportResponseAllSuccesses() {
        // Arrange
        List<FileImportResponse> results = new ArrayList<>();
        results.add(FileImportResponse.success(UUID.randomUUID(), "Story 1", "EPUB"));
        results.add(FileImportResponse.success(UUID.randomUUID(), "Story 2", "PDF"));

        // Act
        ZIPImportResponse response = ZIPImportResponse.create(results);

        // Assert
        assertNotNull(response);
        assertEquals(2, response.getTotalFiles());
        assertEquals(2, response.getSuccessfulImports());
        assertEquals(0, response.getFailedImports());
        assertTrue(response.isSuccess());
        assertTrue(response.getMessage().contains("All files imported successfully"));
    }
}
