package org.micord.controller;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.micord.enums.ConfigType;
import org.micord.models.requests.RequestParameters;
import org.micord.service.ApiService;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ApiControllerTest {

    @Mock
    private ApiService apiService;

    @InjectMocks
    private ApiController apiController;

    @TempDir
    Path tempDir;

    private RequestParameters testRequest;

    @BeforeEach
    void setUp() {
        testRequest = new RequestParameters();
        testRequest.setType("test-download-type");
        testRequest.setIds(Arrays.asList("id1", "id2", "id3"));
        testRequest.setStartDate(LocalDate.of(2024, 1, 1));
        testRequest.setEndDate(LocalDate.of(2024, 12, 31));
    }

    @Test
    void testDownloadCSV_SingleCsvFile() throws Exception {
        // Given: Service returns a single CSV file
        File csvFile = createTempCsvFile("test-download.csv");
        when(apiService.download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class)))
                .thenReturn(csvFile);

        // When: Calling downloadCSV endpoint
        ResponseEntity<Resource> response = apiController.downloadCSV(testRequest);

        // Then: Response should have CSV content type and headers
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getHeaders().getContentType())
                .isEqualTo(MediaType.parseMediaType("text/csv; charset=UTF-8"));
        assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION))
                .startsWith("attachment; filename=")
                .endsWith(".csv");
        assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING))
                .isEqualTo("UTF-8");
        assertThat(response.getHeaders().getContentLength()).isEqualTo(csvFile.length());

        verify(apiService).download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class));
    }

    @Test
    void testDownloadCSV_ZipFile() throws Exception {
        // Given: Service returns a ZIP file (for large datasets)
        File zipFile = createTempZipFile("test-download.zip");
        when(apiService.download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class)))
                .thenReturn(zipFile);

        // When: Calling downloadCSV endpoint
        ResponseEntity<Resource> response = apiController.downloadCSV(testRequest);

        // Then: Response should have ZIP content type and headers
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getHeaders().getContentType())
                .isEqualTo(MediaType.APPLICATION_OCTET_STREAM);
        assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION))
                .startsWith("attachment; filename=")
                .endsWith(".zip");
        // Content-Encoding should NOT be set for ZIP files
        assertThat(response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING))
                .isNull();
        assertThat(response.getHeaders().getContentLength()).isEqualTo(zipFile.length());

        verify(apiService).download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class));
    }

    @Test
    void testDownloadCSV_HandlesIOException() throws Exception {
        // Given: Service throws IOException
        when(apiService.download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class)))
                .thenThrow(new IOException("Test IO error"));

        // When/Then: Should propagate the exception
        assertThrows(IOException.class, () -> {
            apiController.downloadCSV(testRequest);
        });
    }

    @Test
    void testDownloadCSV_EmptyRequest() throws Exception {
        // Given: Empty request parameters
        RequestParameters emptyRequest = new RequestParameters();
        emptyRequest.setType("empty-type");

        File csvFile = createTempCsvFile("empty.csv");
        when(apiService.download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class)))
                .thenReturn(csvFile);

        // When: Calling with empty request
        ResponseEntity<Resource> response = apiController.downloadCSV(emptyRequest);

        // Then: Should still process successfully
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isNotNull();
    }

    @Test
    void testListDownloadTypes() throws Exception {
        // Given: Service returns list of download types
        List<String> downloadTypes = Arrays.asList(
                "TYPE_1", "TYPE_2", "TYPE_3", "TYPE_4"
        );
        when(apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV))
                .thenReturn(downloadTypes);

        // When: Calling listDownloadTypes endpoint
        ResponseEntity<List<String>> response = apiController.listDownloadTypes();

        // Then: Should return the list
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isEqualTo(downloadTypes);
        assertThat(response.getBody()).hasSize(4);

        verify(apiService).getDownloadTypes(ConfigType.DOWNLOAD_CSV);
    }

    @Test
    void testRemoveMilitaryDraftNotices() throws Exception {
        // Given: Request with IDs
        RequestParameters request = new RequestParameters();
        request.setIds(Arrays.asList("notice1", "notice2"));

        doNothing().when(apiService).process(eq(ConfigType.REMOVE_MILITARY_DRAFT_NOTICES), any(RequestParameters.class));

        // When: Calling removeMilitaryDraftNotices
        ResponseEntity<?> response = apiController.removeMilitaryDraftNotices(request);

        // Then: Should return success message
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isEqualTo("Операция \"Удаление повесток\" завершена успешно.");

        verify(apiService).process(eq(ConfigType.REMOVE_MILITARY_DRAFT_NOTICES), any(RequestParameters.class));
    }

    @Test
    void testDeleteFiles() throws Exception {
        // Given: List of file IDs
        List<String> fileIds = Arrays.asList("file1", "file2", "file3");

        doNothing().when(apiService).process(eq(ConfigType.DELETE_FILES), eq(fileIds));

        // When: Calling deleteFiles
        ResponseEntity<?> response = apiController.deleteFiles(fileIds);

        // Then: Should return success message
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isEqualTo("Операция \"Удаление файлов\" завершена успешно.");

        verify(apiService).process(ConfigType.DELETE_FILES, fileIds);
    }

    @Test
    void testBlock() throws Exception {
        // Given: List of IDs to block
        List<String> ids = Arrays.asList("user1", "user2");

        doNothing().when(apiService).process(eq(ConfigType.BLOCK), eq(ids));

        // When: Calling block
        ResponseEntity<?> response = apiController.block(ids);

        // Then: Should return success message
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isEqualTo("Операция \"Блокировка\" завершена успешно.");

        verify(apiService).process(ConfigType.BLOCK, ids);
    }

    @Test
    void testUnblock() throws Exception {
        // Given: List of IDs to unblock
        List<String> ids = Arrays.asList("user1", "user2");

        doNothing().when(apiService).process(eq(ConfigType.UNBLOCK), eq(ids));

        // When: Calling unblock
        ResponseEntity<?> response = apiController.unblock(ids);

        // Then: Should return success message
        assertThat(response.getStatusCodeValue()).isEqualTo(200);
        assertThat(response.getBody()).isEqualTo("Операция \"Разблокировка\" завершена успешно.");

        verify(apiService).process(ConfigType.UNBLOCK, ids);
    }

    @Test
    void testContentTypeDetection() throws Exception {
        // Test various file extensions
        testFileContentType("data.csv", MediaType.parseMediaType("text/csv; charset=UTF-8"));
        testFileContentType("archive.zip", MediaType.APPLICATION_OCTET_STREAM);
        testFileContentType("report.CSV", MediaType.parseMediaType("text/csv; charset=UTF-8"));
        testFileContentType("bundle.ZIP", MediaType.APPLICATION_OCTET_STREAM);
    }

    private void testFileContentType(String filename, MediaType expectedType) throws Exception {
        // Create file with specific extension
        File file = tempDir.resolve(filename).toFile();
        file.createNewFile();

        when(apiService.download(eq(ConfigType.DOWNLOAD_CSV), any(RequestParameters.class)))
                .thenReturn(file);

        ResponseEntity<Resource> response = apiController.downloadCSV(testRequest);

        assertThat(response.getHeaders().getContentType()).isEqualTo(expectedType);
    }

    // Helper methods

    private File createTempCsvFile(String filename) throws IOException {
        File csvFile = Files.createTempFile(tempDir, filename.replace(".csv", ""), ".csv").toFile();

        try (FileOutputStream fos = new FileOutputStream(csvFile)) {
            String csvContent = "header1,header2,header3\n" +
                    "value1,value2,value3\n" +
                    "value4,value5,value6\n";
            fos.write(csvContent.getBytes(StandardCharsets.UTF_8));
        }

        return csvFile;
    }

    private File createTempZipFile(String filename) throws IOException {
        File zipFile = Files.createTempFile(tempDir, filename.replace(".zip", ""), ".zip").toFile();

        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
            // Add first CSV
            ZipEntry entry1 = new ZipEntry("part1.csv");
            zos.putNextEntry(entry1);
            zos.write("header1,header2\ndata1,data2\n".getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();

            // Add second CSV
            ZipEntry entry2 = new ZipEntry("part2.csv");
            zos.putNextEntry(entry2);
            zos.write("header1,header2\ndata3,data4\n".getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();
        }

        return zipFile;
    }
}