"""
Tests for RISExporter._create_ris_entry() edge cases.

Existing tests cover: basic ID, year extraction, DOI from metadata,
GitHub publisher, and arXiv publisher.

This file adds coverage for the many remaining branches:
- Author parsing (split by 'and', '&', commas)
- DOI extraction from text (not just metadata)
- Publisher detection for Reddit, YouTube, Medium, PyPI, generic domains
- www. prefix stripping
- Title cleaning (DOI, "Published in", "Volume", "Pages" patterns)
- Empty/minimal inputs
- Author match overlapping with title cleaning
"""

import re

from freezegun import freeze_time

from local_deep_research.text_optimization.citation_formatter import RISExporter


class TestRISEntryAuthorParsing:
    """Tests for author extraction from 'by Author1, Author2' patterns."""

    def _make(self):
        return RISExporter()

    def test_single_author(self):
        """Extracts a single author."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Some Title\nWritten by Jane Doe."
        )
        assert "AU  - Jane Doe" in result

    def test_authors_separated_by_and(self):
        """Authors separated by 'and' are split correctly."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\nby Alice Smith and Bob Jones."
        )
        assert "AU  - Alice Smith" in result
        assert "AU  - Bob Jones" in result

    def test_authors_separated_by_ampersand(self):
        """Authors separated by '&' are split correctly."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\nby Alice Smith & Bob Jones."
        )
        assert "AU  - Alice Smith" in result
        assert "AU  - Bob Jones" in result

    def test_authors_separated_by_comma(self):
        """Authors separated by commas are split correctly."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\nby Alice Smith, Bob Jones, Carol White."
        )
        assert "AU  - Alice Smith" in result
        assert "AU  - Bob Jones" in result
        assert "AU  - Carol White" in result

    def test_authors_mixed_separators(self):
        """Authors with mixed 'and' and comma separators."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\nby Alice, Bob and Carol."
        )
        assert "AU  - Alice" in result
        assert "AU  - Bob" in result
        assert "AU  - Carol" in result

    def test_no_authors(self):
        """No author line produces no AU entries."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title with no authors")
        assert "AU  -" not in result

    def test_by_keyword_case_insensitive(self):
        """The 'by' keyword match is case-insensitive."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title\nBY John Smith.")
        assert "AU  - John Smith" in result

    def test_empty_author_parts_filtered(self):
        """Empty parts from splitting are filtered out."""
        exporter = self._make()
        # Trailing comma leaves an empty part
        result = exporter._create_ris_entry("1", "Title\nby Alice, , Bob.")
        lines = [
            line for line in result.split("\n") if line.startswith("AU  -")
        ]
        # Should only have real authors, not empty strings
        for line in lines:
            assert line.strip() != "AU  -"


class TestRISEntryDOIExtraction:
    """Tests for DOI extraction from metadata and text."""

    def _make(self):
        return RISExporter()

    def test_doi_from_metadata_takes_precedence(self):
        """DOI from metadata dict is used over DOI in text."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1",
            "Title DOI: 10.9999/text-doi",
            metadata={"doi": "10.1234/meta-doi"},
        )
        assert "DO  - 10.1234/meta-doi" in result
        assert (
            "10.9999/text-doi" not in result.split("DO  - ")[1].split("\n")[0]
        )

    def test_doi_from_text(self):
        """DOI extracted from text when not in metadata."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\nDOI: 10.1234/test.5678"
        )
        assert "DO  - 10.1234/test.5678" in result

    def test_doi_from_text_case_insensitive(self):
        """DOI extraction from text is case-insensitive."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title\ndoi: 10.1234/lowercase"
        )
        assert "DO  - 10.1234/lowercase" in result

    def test_no_doi(self):
        """No DOI produces no DO entry."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title with no DOI")
        assert "DO  -" not in result

    def test_metadata_none_falls_back_to_text(self):
        """When metadata is None, DOI is extracted from text."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title\nDOI: 10.5555/fallback")
        assert "DO  - 10.5555/fallback" in result

    def test_metadata_without_doi_key(self):
        """Metadata dict without 'doi' key falls back to text."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1",
            "Title\nDOI: 10.7777/textdoi",
            metadata={"other": "value"},
        )
        assert "DO  - 10.7777/textdoi" in result


class TestRISEntryPublisherDetection:
    """Tests for publisher extraction from URL domains."""

    def _make(self):
        return RISExporter()

    def test_reddit_publisher(self):
        """Reddit URL produces Reddit publisher."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://www.reddit.com/r/python/post"
        )
        assert "PB  - Reddit" in result

    def test_reddit_subdomain(self):
        """Reddit subdomain also detected."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://old.reddit.com/r/science"
        )
        assert "PB  - Reddit" in result

    def test_youtube_publisher(self):
        """YouTube URL produces YouTube publisher."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://www.youtube.com/watch?v=abc123"
        )
        assert "PB  - YouTube" in result

    def test_youtube_mobile(self):
        """Mobile YouTube URL produces YouTube publisher."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://m.youtube.com/watch?v=abc123"
        )
        assert "PB  - YouTube" in result

    def test_medium_publisher(self):
        """Medium URL produces Medium publisher."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://medium.com/@author/article"
        )
        assert "PB  - Medium" in result

    def test_medium_subdomain(self):
        """Medium subdomain (custom publication) detected."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://blog.medium.com/post"
        )
        assert "PB  - Medium" in result

    def test_pypi_publisher(self):
        """PyPI URL produces PyPI publisher."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://pypi.org/project/requests/"
        )
        assert "PB  - Python Package Index (PyPI)" in result

    def test_generic_domain_as_publisher(self):
        """Unknown domain used as publisher name."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://example.com/article"
        )
        assert "PB  - example.com" in result

    def test_www_stripped_from_domain(self):
        """www. prefix is stripped from domain."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://www.example.org/page"
        )
        assert "PB  - example.org" in result

    def test_no_url_no_publisher(self):
        """No URL produces no PB or UR entries."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        assert "PB  -" not in result
        assert "UR  -" not in result

    def test_empty_url_no_publisher(self):
        """Empty string URL produces no PB or UR entries."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title", url="")
        assert "PB  -" not in result
        assert "UR  -" not in result

    def test_github_subdomain(self):
        """GitHub subdomain detected as GitHub."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://pages.github.com/project"
        )
        assert "PB  - GitHub" in result

    def test_arxiv_subdomain(self):
        """arXiv subdomain detected as arXiv."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://export.arxiv.org/abs/1234"
        )
        assert "PB  - arXiv" in result


class TestRISEntryTitleCleaning:
    """Tests for title cleaning patterns."""

    def _make(self):
        return RISExporter()

    def test_doi_removed_from_title(self):
        """DOI text is removed from the title."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "My Paper DOI: 10.1234/test\nMore content"
        )
        # The title line should not contain DOI
        ti_line = [
            line for line in result.split("\n") if line.startswith("TI  -")
        ][0]
        assert "DOI:" not in ti_line

    def test_published_in_removed_from_title(self):
        """'Published in ...' is removed from the title."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "My Paper Published in Nature 2023\nContent"
        )
        ti_line = [
            line for line in result.split("\n") if line.startswith("TI  -")
        ][0]
        assert "Published in" not in ti_line

    def test_volume_removed_from_title(self):
        """'Volume ...' is removed from the title."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "My Paper Volume 42 Issue 3\nContent"
        )
        ti_line = [
            line for line in result.split("\n") if line.startswith("TI  -")
        ][0]
        assert "Volume" not in ti_line

    def test_pages_removed_from_title(self):
        """'Pages ...' is removed from the title."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "My Paper Pages 100-120\nContent"
        )
        ti_line = [
            line for line in result.split("\n") if line.startswith("TI  -")
        ][0]
        assert "Pages" not in ti_line

    def test_fallback_to_original_title_when_clean_empty(self):
        """If cleaning empties the title, original title is used."""
        exporter = self._make()
        # Title that's entirely "by Author" where author match starts at 0
        result = exporter._create_ris_entry("1", "by John Smith\nContent")
        # Should still have a TI entry (either cleaned or fallback)
        ti_line = [
            line for line in result.split("\n") if line.startswith("TI  -")
        ][0]
        assert "TI  - " in ti_line


class TestRISEntryStructure:
    """Tests for overall RIS entry structure."""

    def _make(self):
        return RISExporter()

    def test_contains_ty_elec(self):
        """Entry contains TY - ELEC type marker."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        assert "TY  - ELEC" in result

    def test_ends_with_er(self):
        """Entry ends with ER marker."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        lines = result.split("\n")
        assert lines[-1] == "ER  - "

    def test_contains_language(self):
        """Entry contains language tag."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        assert "LA  - en" in result

    @freeze_time("2025-06-15")
    def test_contains_access_year(self):
        """Entry contains current access year."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        assert "Y1  - 2025" in result

    @freeze_time("2025-06-15")
    def test_contains_access_date(self):
        """Entry contains formatted access date."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title")
        assert "DA  - 2025/06/15" in result

    def test_ref_id_format(self):
        """Reference ID formatted as 'refN'."""
        exporter = self._make()
        result = exporter._create_ris_entry("42", "Title")
        assert "ID  - ref42" in result


class TestRISEntryYearExtraction:
    """Tests for year extraction from text."""

    def _make(self):
        return RISExporter()

    def test_year_1990s(self):
        """Extracts 1990s year."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Published in 1995")
        assert "PY  - 1995" in result

    def test_year_2000s(self):
        """Extracts 2000s year."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Released 2003")
        assert "PY  - 2003" in result

    def test_no_year(self):
        """No year in text produces no PY entry."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Title with no date")
        assert "PY  -" not in result

    def test_first_year_extracted(self):
        """First valid year in text is extracted."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Written in 2020, revised in 2023"
        )
        assert "PY  - 2020" in result

    def test_year_boundary_1900(self):
        """Year 1900 is valid (19xx pattern)."""
        exporter = self._make()
        result = exporter._create_ris_entry("1", "Historical text from 1900")
        assert "PY  - 1900" in result

    def test_non_year_four_digit_ignored(self):
        """Four-digit numbers outside 19xx-20xx range are not matched."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Article number 3456 in series"
        )
        assert "PY  -" not in result


class TestRISEntryURLHandling:
    """Tests for URL handling in RIS entries."""

    def _make(self):
        return RISExporter()

    def test_url_included(self):
        """URL is included as UR entry."""
        exporter = self._make()
        result = exporter._create_ris_entry(
            "1", "Title", url="https://example.com"
        )
        assert "UR  - https://example.com" in result

    def test_url_with_path(self):
        """Full URL with path is preserved."""
        exporter = self._make()
        url = "https://example.com/path/to/article?id=123"
        result = exporter._create_ris_entry("1", "Title", url=url)
        assert f"UR  - {url}" in result


class TestRISEntryStructuralValidity:
    """Regression guard for the accumulator-clobber bug.

    ``_create_ris_entry`` initialized ``lines = []`` as the RIS-output
    accumulator, then immediately reassigned ``lines = full_text.split("\\n")``
    to read the title — overwriting the accumulator with the raw source body.
    Every RIS field was then appended *after* those source lines, so each entry
    emitted the source text before the mandatory leading ``TY  - `` tag and
    reference managers (Zotero/Mendeley/EndNote) rejected the record. The
    pre-existing tests only used ``"<tag>" in result`` substring checks, so they
    never caught the leaked prefix.
    """

    # RIS records are newline-separated ``XX  - value`` lines where the tag is
    # two uppercase-or-digit chars (TY, ID, TI, AU, DO, PY, UR, PB, Y1, DA, LA,
    # ER), two spaces, a hyphen, then a space.
    _RIS_LINE = re.compile(r"^[A-Z][A-Z0-9]  - ")

    def test_entry_starts_with_ty_tag(self):
        """RIS requires ``TY`` to be the first tag of the record."""
        exporter = RISExporter()
        source = (
            "Understanding Widgets\n"
            "URL: https://example.com/widgets\n"
            "Collection: Gadgets"
        )
        result = exporter._create_ris_entry(
            "7", source, url="https://example.com/widgets"
        )
        assert result.startswith("TY  - "), result

    def test_no_raw_source_lines_leak(self):
        """Every non-blank output line is a RIS tag, never raw source body."""
        exporter = RISExporter()
        source = (
            "My Title\n"
            "BODYLEAKMARKER this descriptive line must never be emitted\n"
            "Collection: Internal"
        )
        result = exporter._create_ris_entry("1", source)
        assert "BODYLEAKMARKER" not in result
        for line in result.split("\n"):
            if not line.strip():
                continue
            assert self._RIS_LINE.match(line), f"non-RIS line leaked: {line!r}"
