"""Tests for coBib's ISBNParser."""

from __future__ import annotations

import json
import logging
from typing import Dict, Optional

import pytest
import requests

from cobib.config import Event
from cobib.database import Author, Entry
from cobib.parsers import ISBNParser

from .parser_test import ParserTest


def assert_default_test_entry(entry: Entry) -> None:
    """Asserts that the passed entry is the default testing entry.

    Args:
        entry: the entry to assert.
    """
    assert entry.label == "Lutz2013"
    assert entry.data["author"] == [Author("Mark", "Lutz"), Author("David", "Ascher")]
    assert entry.data["pages"] == 1540
    assert entry.data["title"] == "Learning Python"
    assert entry.data["year"] == 2013


class TestISBNParser(ParserTest):
    """Tests for coBib's ISBNParser."""

    def test_from_isbn(self, caplog: pytest.LogCaptureFixture) -> None:
        """Test parsing from ISBN.

        Args:
            caplog: the built-in pytest fixture.
        """
        entries = ISBNParser().parse("978-1-449-35573-9")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        try:
            entry = next(iter(entries.values()))
        except (IndexError, StopIteration):
            pytest.skip("Skipping because we likely ran into a network timeout.")
        assert_default_test_entry(entry)

    # regression test for https://gitlab.com/cobib/cobib/-/issues/53
    def test_from_empty_isbn(self, caplog: pytest.LogCaptureFixture) -> None:
        """Test parsing an empty ISBN.

        Args:
            caplog: the built-in pytest fixture.
        """
        entries = ISBNParser().parse("3860704443")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        assert not entries
        assert entries == {}

        assert ("cobib.parsers.isbn", logging.WARNING) in [
            (source, level) for source, level, _ in caplog.record_tuples
        ]

    def test_invalid_isbn(self, caplog: pytest.LogCaptureFixture) -> None:
        """Test parsing an invalid ISBN.

        Args:
            caplog: the built-in pytest fixture.
        """
        entries = ISBNParser().parse("1701.08213")
        assert not entries
        assert entries == {}

        assert (
            "cobib.parsers.isbn",
            logging.WARNING,
            "'1701.08213' is not a valid ISBN.",
        ) in caplog.record_tuples

    def test_catching_api_error(
        self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test catching API error.

        Args:
            caplog: the built-in pytest fixture.
            monkeypatch: the built-in pytest fixture.
        """

        def raise_exception(*args, **kwargs):  # type: ignore[no-untyped-def]
            """Mock function to raise an Exception."""
            raise requests.exceptions.RequestException()

        monkeypatch.setattr(requests.Session, "get", raise_exception)
        ISBNParser().parse("978-1-449-35573-9")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        assert (
            "cobib.parsers.isbn",
            logging.ERROR,
            "An Exception occurred while trying to query the ISBN: 978-1-449-35573-9.",
        ) in caplog.record_tuples

    def test_catching_decode_error(
        self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Test catching json decode error.

        Args:
            caplog: the built-in pytest fixture.
            monkeypatch: the built-in pytest fixture.
        """

        def raise_exception(*args, **kwargs):  # type: ignore[no-untyped-def]
            """Mock function to raise an Exception."""
            raise json.JSONDecodeError("", "", 0)

        monkeypatch.setattr(json, "loads", raise_exception)
        ISBNParser().parse("978-1-449-35573-9")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        for record in caplog.record_tuples:
            if (
                record[0] == "cobib.parsers.isbn"
                and record[1] == logging.ERROR
                and "An Exception occurred while parsing the query results:" in record[2]
            ):
                break
        else:
            pytest.fail("No Error caught by ISBNParser")

    def test_dump(self, caplog: pytest.LogCaptureFixture) -> None:
        """Test dumping.

        Args:
            caplog: the built-in pytest fixture.
        """
        entry = Entry("dummy", {"ENTRYTYPE": "unpublished"})
        ISBNParser().dump(entry)

        assert (
            "cobib.parsers.isbn",
            logging.ERROR,
            "Cannot dump an entry as an ISBN.",
        ) in caplog.record_tuples

    def test_event_pre_isbn_parse(self, caplog: pytest.LogCaptureFixture) -> None:
        """Tests the PreISBNParse event."""

        @Event.PreISBNParse.subscribe
        def hook(string: str) -> Optional[str]:
            return "978-1-449-35573-9"

        assert Event.PreISBNParse.validate()

        entries = ISBNParser().parse("Hello world!")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        try:
            entry = next(iter(entries.values()))
        except (IndexError, StopIteration):
            pytest.skip("Skipping because we likely ran into a network timeout.")
        assert_default_test_entry(entry)

    def test_event_post_isbn_parse(self, caplog: pytest.LogCaptureFixture) -> None:
        """Tests the PostISBNParse event."""

        @Event.PostISBNParse.subscribe
        def hook(bib: Dict[str, Entry]) -> None:
            bib["Lutz2013"].data["test"] = "dummy"

        assert Event.PostISBNParse.validate()

        entries = ISBNParser().parse("978-1-449-35573-9")

        if any(
            s == "cobib.parsers.isbn" and t == logging.ERROR for s, t, _ in caplog.record_tuples
        ):
            pytest.skip("The requests API encountered an error. Skipping test.")

        try:
            entry = next(iter(entries.values()))
        except (IndexError, StopIteration):
            pytest.skip("Skipping because we likely ran into a network timeout.")
        assert_default_test_entry(entry)
        assert entry.data["test"] == "dummy"
