package com.mosquito.project.service;

import com.mosquito.project.config.PosterConfig;
import com.mosquito.project.domain.Activity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

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

/**
 * PosterRenderService 边界条件和异常处理测试
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("PosterRenderService 边界测试")
class PosterRenderServiceBoundaryTest {

    @Mock
    private PosterConfig posterConfig;

    @Mock
    private ShortLinkService shortLinkService;

    @Mock
    private PosterConfig.PosterTemplate mockTemplate;

    @InjectMocks
    private PosterRenderService posterRenderService;

    private Activity testActivity;

    @BeforeEach
    void setUp() throws Exception {
        testActivity = new Activity();
        testActivity.setId(123L);
        testActivity.setName("测试活动");

        // 清除图片缓存
        Field imageCacheField = PosterRenderService.class.getDeclaredField("imageCache");
        imageCacheField.setAccessible(true);
        Map<String, Image> imageCache = (Map<String, Image>) imageCacheField.get(posterRenderService);
        imageCache.clear();
    }

    @Nested
    @DisplayName("renderPoster边界条件测试")
    class RenderPosterBoundaryTests {

        @Test
        @DisplayName("null模板名应该使用默认模板")
        void shouldUseDefaultTemplate_WhenTemplateNameIsNull() {
            // Given
            String defaultTemplateName = "default";
            when(posterConfig.getTemplate(null)).thenReturn(null);
            when(posterConfig.getTemplate(defaultTemplateName)).thenReturn(mockTemplate);
            when(posterConfig.getDefaultTemplate()).thenReturn(defaultTemplateName);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, null);

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
            verify(posterConfig).getTemplate(null);
            verify(posterConfig).getTemplate(defaultTemplateName);
        }

        @Test
        @DisplayName("不存在的模板名应该使用默认模板")
        void shouldUseDefaultTemplate_WhenTemplateNotFound() {
            // Given
            String invalidTemplateName = "nonexistent";
            String defaultTemplateName = "default";
            when(posterConfig.getTemplate(invalidTemplateName)).thenReturn(null);
            when(posterConfig.getTemplate(defaultTemplateName)).thenReturn(mockTemplate);
            when(posterConfig.getDefaultTemplate()).thenReturn(defaultTemplateName);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, invalidTemplateName);

            // Then
            assertNotNull(result);
            verify(posterConfig).getTemplate(invalidTemplateName);
            verify(posterConfig).getTemplate(defaultTemplateName);
        }

        @Test
        @DisplayName("模板背景为空字符串应该使用背景色")
        void shouldUseBackgroundColor_WhenTemplateBackgroundIsEmpty() {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackground()).thenReturn("");
            when(mockTemplate.getBackgroundColor()).thenReturn("#FF0000");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, "test");

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
        }

        @Test
        @DisplayName("模板背景为null应该使用背景色")
        void shouldUseBackgroundColor_WhenTemplateBackgroundIsNull() {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackground()).thenReturn(null);
            when(mockTemplate.getBackgroundColor()).thenReturn("#00FF00");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, "test");

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
        }

        @Test
        @DisplayName("模板没有背景设置应该使用背景色")
        void shouldUseBackgroundColor_WhenTemplateHasNoBackground() {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#0000FF");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, "test");

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
        }

        @Test
        @DisplayName("图片加载失败应该使用背景色")
        void shouldUseBackgroundColor_WhenImageLoadFails() {
            // Given
            String invalidImageUrl = "nonexistent-image.jpg";
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackground()).thenReturn(invalidImageUrl);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFAA00");
            when(posterConfig.getCdnBaseUrl()).thenReturn("https://cdn.example.com");

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, "test");

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
        }

        @Test
        @DisplayName("空元素列表应该正常渲染")
        void shouldRenderSuccessfully_WhenElementsListIsEmpty() throws Exception {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");
            
            // 设置空元素map
            Field elementsField = PosterConfig.PosterTemplate.class.getDeclaredField("elements");
            elementsField.setAccessible(true);
            elementsField.set(mockTemplate, new ConcurrentHashMap<>());

            // When
            byte[] result = posterRenderService.renderPoster(1L, 1L, "test");

            // Then
            assertNotNull(result);
            assertTrue(result.length > 0);
        }

        @ParameterizedTest
        @ValueSource(strings = {"#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000"})
        @DisplayName("不同背景色应该正确渲染")
        void shouldRenderCorrectly_WithDifferentBackgroundColors(String backgroundColor) {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn(backgroundColor);

            // When & Then
            assertDoesNotThrow(() -> {
                byte[] result = posterRenderService.renderPoster(1L, 1L, "test");
                assertNotNull(result);
                assertTrue(result.length > 0);
            });
        }
    }

    @Nested
    @DisplayName("renderPosterHtml边界条件测试")
    class RenderPosterHtmlBoundaryTests {

        @Test
        @DisplayName("null活动名称应该使用默认值")
        void shouldUseDefaultTitle_WhenActivityNameIsNull() {
            // Given
            Activity nullNameActivity = new Activity();
            nullNameActivity.setId(123L);
            nullNameActivity.setName(null);
            
            setupMockTemplate();
            when(shortLinkService.create(anyString())).thenReturn(new com.mosquito.project.domain.ShortLink());
            when(shortLinkService.create(anyString())).thenReturn(new com.mosquito.project.domain.ShortLink());

            // 使用反射设置模拟活动
            // 注意：这里需要更复杂的反射来模拟activityService的返回值

            // When
            String html = posterRenderService.renderPosterHtml(123L, 456L, "test");

            // Then
            assertNotNull(html);
            assertTrue(html.contains("<title>分享</title>"));
        }

        @Test
        @DisplayName("空活动名称应该使用默认值")
        void shouldUseDefaultTitle_WhenActivityNameIsEmpty() {
            // Given
            Activity emptyNameActivity = new Activity();
            emptyNameActivity.setId(123L);
            emptyNameActivity.setName("");
            
            setupMockTemplate();

            // When
            String html = posterRenderService.renderPosterHtml(123L, 456L, "test");

            // Then
            assertNotNull(html);
            assertTrue(html.contains("<title>分享</title>"));
        }

        @Test
        @DisplayName("活动名称包含特殊字符应该正确转义")
        void shouldEscapeHtml_WhenActivityNameContainsSpecialChars() {
            // Given
            Activity specialCharActivity = new Activity();
            specialCharActivity.setId(123L);
            specialCharActivity.setName("活动名称 & <script> alert('xss') </script>\"");
            
            setupMockTemplate();

            // When
            String html = posterRenderService.renderPosterHtml(123L, 456L, "test");

            // Then
            assertNotNull(html);
            assertFalse(html.contains("<script>"));
            assertTrue(html.contains("&lt;script&gt;"));
        }

        @Test
        @DisplayName("URL编码失败应该使用原始URL")
        void shouldUseOriginalUrl_WhenUrlEncodingFails() {
            // Given
            setupMockTemplate();
            setupQrCodeElement();

            // When
            String html = posterRenderService.renderPosterHtml(123L, 456L, "test");

            // Then
            assertNotNull(html);
            assertTrue(html.contains("data="));
            // 确保即使编码失败也生成HTML
        }

        @Test
        @DisplayName("长活动名称应该正确处理")
        void shouldHandleLongActivityName() {
            // Given
            StringBuilder longName = new StringBuilder();
            for (int i = 0; i < 1000; i++) {
                longName.append("很长的活动名称");
            }
            Activity longNameActivity = new Activity();
            longNameActivity.setId(123L);
            longNameActivity.setName(longName.toString());
            
            setupMockTemplate();

            // When
            String html = posterRenderService.renderPosterHtml(123L, 456L, "test");

            // Then
            assertNotNull(html);
            assertTrue(html.length() > 0);
        }

        private void setupMockTemplate() {
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");
        }

        private void setupQrCodeElement() {
            PosterConfig.PosterElement qrElement = new PosterConfig.PosterElement();
            qrElement.setType("qrcode");
            qrElement.setX(100);
            qrElement.setY(100);
            qrElement.setWidth(200);
            qrElement.setHeight(200);
            
            Map<String, PosterConfig.PosterElement> elements = Map.of("qrcode", qrElement);
            when(mockTemplate.getElements()).thenReturn(elements);
        }
    }

    @Nested
    @DisplayName("异常处理测试")
    class ExceptionHandlingTests {

        @Test
        @DisplayName("ImageIO写入失败应该抛出RuntimeException")
        void shouldThrowRuntimeException_WhenImageIoWriteFails() {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(Integer.MAX_VALUE); // 极大尺寸可能导致内存错误
            when(mockTemplate.getHeight()).thenReturn(Integer.MAX_VALUE);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            // When & Then
            assertThrows(RuntimeException.class, () -> {
                posterRenderService.renderPoster(1L, 1L, "test");
            });
        }

        @Test
        @DisplayName("无效颜色代码应该抛出异常")
        void shouldThrowException_WhenColorCodeIsInvalid() {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("invalid-color");

            // When & Then
            assertThrows(NumberFormatException.class, () -> {
                posterRenderService.renderPoster(1L, 1L, "test");
            });
        }

        @ParameterizedTest
        @NullAndEmptySource
        @DisplayName("null或空模板名应该不抛出异常")
        void shouldNotThrowException_WhenTemplateNameIsNullOrEmpty(String templateName) {
            // Given
            when(posterConfig.getTemplate(templateName)).thenReturn(null);
            when(posterConfig.getTemplate("default")).thenReturn(mockTemplate);
            when(posterConfig.getDefaultTemplate()).thenReturn("default");
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            // When & Then
            assertDoesNotThrow(() -> {
                byte[] result = posterRenderService.renderPoster(1L, 1L, templateName);
                assertNotNull(result);
            });
        }
    }

    @Nested
    @DisplayName("辅助方法边界测试")
    class HelperMethodBoundaryTests {

        @Test
        @DisplayName("解析字体大小失败应该使用默认值")
        void shouldUseDefaultFontSize_WhenParsingFails() throws Exception {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            PosterConfig.PosterElement textElement = new PosterConfig.PosterElement();
            textElement.setType("text");
            textElement.setContent("测试文本");
            textElement.setFontSize("invalid-size"); // 无效的字体大小
            textElement.setColor("#000000");
            textElement.setFontFamily("Arial");
            textElement.setX(100);
            textElement.setY(100);

            Map<String, PosterConfig.PosterElement> elements = Map.of("text", textElement);
            when(mockTemplate.getElements()).thenReturn(elements);

            // When & Then
            assertDoesNotThrow(() -> {
                byte[] result = posterRenderService.renderPoster(1L, 1L, "test");
                assertNotNull(result);
            });
        }

        @Test
        @DisplayName("空内容字符串应该正确处理")
        void shouldHandleEmptyContentString() throws Exception {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            PosterConfig.PosterElement textElement = new PosterConfig.PosterElement();
            textElement.setType("text");
            textElement.setContent(""); // 空内容
            textElement.setFontSize("16px");
            textElement.setColor("#000000");
            textElement.setFontFamily("Arial");
            textElement.setX(100);
            textElement.setY(100);

            Map<String, PosterConfig.PosterElement> elements = Map.of("text", textElement);
            when(mockTemplate.getElements()).thenReturn(elements);

            // When & Then
            assertDoesNotThrow(() -> {
                byte[] result = posterRenderService.renderPoster(1L, 1L, "test");
                assertNotNull(result);
            });
        }

        @Test
        @DisplayName("null内容应该返回空字符串")
        void shouldReturnEmptyString_WhenContentIsNull() throws Exception {
            // Given
            when(posterConfig.getTemplate(anyString())).thenReturn(mockTemplate);
            when(mockTemplate.getWidth()).thenReturn(800);
            when(mockTemplate.getHeight()).thenReturn(600);
            when(mockTemplate.getBackgroundColor()).thenReturn("#FFFFFF");

            PosterConfig.PosterElement textElement = new PosterConfig.PosterElement();
            textElement.setType("text");
            textElement.setContent(null); // null内容
            textElement.setFontSize("16px");
            textElement.setColor("#000000");
            textElement.setFontFamily("Arial");
            textElement.setX(100);
            textElement.setY(100);

            Map<String, PosterConfig.PosterElement> elements = Map.of("text", textElement);
            when(mockTemplate.getElements()).thenReturn(elements);

            // When & Then
            assertDoesNotThrow(() -> {
                byte[] result = posterRenderService.renderPoster(1L, 1L, "test");
                assertNotNull(result);
            });
        }
    }
}