package com.pandoli365.bibimbap.service;

import com.pandoli365.bibimbap.service.FeedParser.FeedItem;
import org.junit.jupiter.api.Test;

import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class FeedParserTest {

    private final FeedParser parser = new FeedParser();

    @Test
    void parsesRss20() {
        String xml = """
                <?xml version="1.0" encoding="UTF-8"?>
                <rss version="2.0">
                  <channel>
                    <title>Unity Blog</title>
                    <item>
                      <guid>urn:unity:1</guid>
                      <title>First Post</title>
                      <link>https://unity.com/blog/first</link>
                      <pubDate>Tue, 24 Jun 2026 10:00:00 GMT</pubDate>
                    </item>
                    <item>
                      <guid>urn:unity:2</guid>
                      <title>Second Post</title>
                      <link>https://unity.com/blog/second</link>
                      <pubDate>Wed, 25 Jun 2026 12:30:00 GMT</pubDate>
                    </item>
                  </channel>
                </rss>
                """;

        List<FeedItem> items = parser.parse(xml.getBytes(UTF_8));

        assertEquals(2, items.size());
        FeedItem first = items.get(0);
        assertEquals("urn:unity:1", first.guid());
        assertEquals("First Post", first.title());
        assertEquals("https://unity.com/blog/first", first.link());
        assertNotNull(first.publishedAt());
        FeedItem second = items.get(1);
        assertEquals("urn:unity:2", second.guid());
        assertEquals("Second Post", second.title());
        assertEquals("https://unity.com/blog/second", second.link());
    }

    @Test
    void parsesRssGuidFallsBackToLink() {
        String xml = """
                <?xml version="1.0" encoding="UTF-8"?>
                <rss version="2.0">
                  <channel>
                    <item>
                      <title>No Guid</title>
                      <link>https://unity.com/blog/no-guid</link>
                    </item>
                  </channel>
                </rss>
                """;

        List<FeedItem> items = parser.parse(xml.getBytes(UTF_8));

        assertEquals(1, items.size());
        assertEquals("https://unity.com/blog/no-guid", items.get(0).guid());
    }

    @Test
    void parsesAtom10() {
        String xml = """
                <?xml version="1.0" encoding="UTF-8"?>
                <feed xmlns="http://www.w3.org/2005/Atom">
                  <title>Unity News</title>
                  <entry>
                    <id>tag:unity,2026:a</id>
                    <title>Atom First</title>
                    <link rel="alternate" href="https://unity.com/news/a"/>
                    <updated>2026-06-24T10:00:00Z</updated>
                  </entry>
                  <entry>
                    <id>tag:unity,2026:b</id>
                    <title>Atom Second</title>
                    <link rel="alternate" href="https://unity.com/news/b"/>
                    <published>2026-06-25T12:30:00Z</published>
                  </entry>
                </feed>
                """;

        List<FeedItem> items = parser.parse(xml.getBytes(UTF_8));

        assertEquals(2, items.size());
        FeedItem first = items.get(0);
        assertEquals("tag:unity,2026:a", first.guid());
        assertEquals("Atom First", first.title());
        assertEquals("https://unity.com/news/a", first.link());
        assertNotNull(first.publishedAt());
        FeedItem second = items.get(1);
        assertEquals("tag:unity,2026:b", second.guid());
        assertEquals("Atom Second", second.title());
        assertEquals("https://unity.com/news/b", second.link());
        assertNotNull(second.publishedAt());
    }

    @Test
    void atomPrefersAlternateLink() {
        String xml = """
                <?xml version="1.0" encoding="UTF-8"?>
                <feed xmlns="http://www.w3.org/2005/Atom">
                  <entry>
                    <id>tag:unity,2026:c</id>
                    <title>Multi Link</title>
                    <link rel="self" href="https://unity.com/news/self"/>
                    <link rel="alternate" href="https://unity.com/news/alternate"/>
                  </entry>
                </feed>
                """;

        List<FeedItem> items = parser.parse(xml.getBytes(UTF_8));

        assertEquals(1, items.size());
        assertEquals("https://unity.com/news/alternate", items.get(0).link());
    }

    @Test
    void blocksXxeExternalEntity() {
        // DOCTYPE + external entity 포함 악성 XML — disallow-doctype-decl 로 파싱 거부 기대
        String malicious = """
                <?xml version="1.0" encoding="UTF-8"?>
                <!DOCTYPE rss [
                  <!ENTITY xxe SYSTEM "file:///etc/passwd">
                ]>
                <rss version="2.0">
                  <channel>
                    <item>
                      <guid>x</guid>
                      <title>&xxe;</title>
                      <link>https://example.com/x</link>
                    </item>
                  </channel>
                </rss>
                """;

        List<FeedItem> items = assertDoesNotThrow(() -> parser.parse(malicious.getBytes(UTF_8)));

        // DOCTYPE 거부로 빈 리스트. 만약 파싱됐더라도 entity 가 확장되어 시스템 파일 내용이
        // 노출되어선 안 된다.
        if (!items.isEmpty()) {
            String title = items.get(0).title();
            assertFalse(title != null && title.contains("root:"),
                    "external entity 가 확장되어선 안 된다");
        } else {
            assertTrue(items.isEmpty());
        }
    }

    @Test
    void returnsEmptyForEmptyInput() {
        assertTrue(parser.parse(new byte[0]).isEmpty());
        assertTrue(parser.parse(null).isEmpty());
    }

    @Test
    void returnsEmptyForBrokenInput() {
        byte[] notXml = "this is not xml at all <<< >>>".getBytes(UTF_8);
        List<FeedItem> items = assertDoesNotThrow(() -> parser.parse(notXml));
        assertTrue(items.isEmpty());
    }
}
