"""Tests for the input_readers module."""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pandas as pd
import pytest

from earthcatalog.input_readers import (
    CSVReader,
    InputReader,
    NDJSONReader,
    ParquetReader,
    ReaderFactory,
)


class TestParquetReader:
    """Tests for ParquetReader class."""

    @pytest.fixture
    def sample_parquet_file(self, tmp_path: Path) -> Path:
        """Create a temporary parquet file with URLs."""
        df = pd.DataFrame(
            {
                "url": [
                    "https://example.com/item1.json",
                    "https://example.com/item2.json",
                    "https://example.com/item3.json",
                ],
                "name": ["item1", "item2", "item3"],
            }
        )
        path = tmp_path / "urls.parquet"
        df.to_parquet(path, index=False)
        return path

    @pytest.fixture
    def empty_parquet_file(self, tmp_path: Path) -> Path:
        """Create an empty parquet file."""
        df = pd.DataFrame({"url": []})
        path = tmp_path / "empty.parquet"
        df.to_parquet(path, index=False)
        return path

    def test_read_urls_basic(self, sample_parquet_file: Path):
        """Test reading URLs from parquet file."""
        reader = ParquetReader()
        urls = reader.read_urls(str(sample_parquet_file), "url")
        assert len(urls) == 3
        assert all(u.startswith("https://") for u in urls)
        assert "https://example.com/item1.json" in urls

    def test_read_urls_custom_column(self, tmp_path: Path):
        """Test reading from custom URL column name."""
        df = pd.DataFrame({"stac_url": ["https://example.com/item.json"]})
        path = tmp_path / "custom.parquet"
        df.to_parquet(path, index=False)

        reader = ParquetReader()
        urls = reader.read_urls(str(path), "stac_url")
        assert len(urls) == 1
        assert urls[0] == "https://example.com/item.json"

    def test_read_urls_missing_column(self, sample_parquet_file: Path):
        """Test error when URL column doesn't exist."""
        reader = ParquetReader()
        with pytest.raises(ValueError, match="must contain"):
            reader.read_urls(str(sample_parquet_file), "nonexistent_column")

    def test_read_urls_file_not_found(self):
        """Test error when file doesn't exist."""
        reader = ParquetReader()
        with pytest.raises(FileNotFoundError):
            reader.read_urls("/nonexistent/path/to/file.parquet", "url")

    def test_read_urls_empty_file(self, empty_parquet_file: Path):
        """Test reading from empty parquet file."""
        reader = ParquetReader()
        urls = reader.read_urls(str(empty_parquet_file), "url")
        assert len(urls) == 0
        assert isinstance(urls, list)

    def test_validate_format_valid(self, sample_parquet_file: Path):
        """Test format validation for valid parquet."""
        reader = ParquetReader()
        assert reader.validate_format(str(sample_parquet_file)) is True

    def test_validate_format_invalid(self, tmp_path: Path):
        """Test format validation for invalid file."""
        invalid = tmp_path / "invalid.txt"
        invalid.write_text("this is not a parquet file")
        reader = ParquetReader()
        assert reader.validate_format(str(invalid)) is False

    def test_validate_format_nonexistent(self):
        """Test format validation for nonexistent file."""
        reader = ParquetReader()
        assert reader.validate_format("/nonexistent/file.parquet") is False

    def test_read_urls_preserves_order(self, tmp_path: Path):
        """Test that URL order is preserved."""
        urls = [f"https://example.com/item{i}.json" for i in range(100)]
        df = pd.DataFrame({"url": urls})
        path = tmp_path / "ordered.parquet"
        df.to_parquet(path, index=False)

        reader = ParquetReader()
        result = reader.read_urls(str(path), "url")
        assert result == urls


class TestCSVReader:
    """Tests for CSVReader class."""

    @pytest.fixture
    def sample_csv_file(self, tmp_path: Path) -> Path:
        """Create a temporary CSV file with URLs."""
        path = tmp_path / "urls.csv"
        path.write_text("url,name\nhttps://example.com/1.json,item1\nhttps://example.com/2.json,item2\n")
        return path

    @pytest.fixture
    def empty_csv_file(self, tmp_path: Path) -> Path:
        """Create an empty CSV file (header only)."""
        path = tmp_path / "empty.csv"
        path.write_text("url,name\n")
        return path

    def test_read_urls_basic(self, sample_csv_file: Path):
        """Test reading URLs from CSV file."""
        reader = CSVReader()
        urls = reader.read_urls(str(sample_csv_file), "url")
        assert len(urls) == 2
        assert "https://example.com/1.json" in urls
        assert "https://example.com/2.json" in urls

    def test_read_urls_custom_delimiter(self, tmp_path: Path):
        """Test reading with custom delimiter (pipe)."""
        path = tmp_path / "urls.psv"
        path.write_text("url|name\nhttps://example.com/1.json|item1\n")
        reader = CSVReader(delimiter="|")
        urls = reader.read_urls(str(path), "url")
        assert len(urls) == 1
        assert urls[0] == "https://example.com/1.json"

    def test_read_urls_tab_delimiter(self, tmp_path: Path):
        """Test reading TSV file."""
        path = tmp_path / "urls.tsv"
        path.write_text("url\tname\nhttps://example.com/1.json\titem1\n")
        reader = CSVReader(delimiter="\t")
        urls = reader.read_urls(str(path), "url")
        assert len(urls) == 1

    def test_read_urls_empty_file(self, empty_csv_file: Path):
        """Test reading empty CSV file (header only)."""
        reader = CSVReader()
        urls = reader.read_urls(str(empty_csv_file), "url")
        assert len(urls) == 0

    def test_read_urls_missing_column(self, sample_csv_file: Path):
        """Test error when URL column doesn't exist."""
        reader = CSVReader()
        with pytest.raises(ValueError, match="must contain"):
            reader.read_urls(str(sample_csv_file), "nonexistent")

    def test_read_urls_with_null_values(self, tmp_path: Path):
        """Test that null URL values are filtered out."""
        path = tmp_path / "with_nulls.csv"
        path.write_text("url,name\nhttps://example.com/1.json,item1\n,item2\nhttps://example.com/3.json,item3\n")
        reader = CSVReader()
        urls = reader.read_urls(str(path), "url")
        # Should have 2 URLs (the empty one filtered out)
        assert len(urls) == 2
        assert "" not in urls

    def test_validate_format_valid(self, sample_csv_file: Path):
        """Test format validation for valid CSV."""
        reader = CSVReader()
        assert reader.validate_format(str(sample_csv_file)) is True

    def test_validate_format_invalid_delimiter(self, tmp_path: Path):
        """Test validation when delimiter doesn't match."""
        path = tmp_path / "wrong_delim.csv"
        path.write_text("url|name\nhttps://example.com/1.json|item1\n")
        # With default comma delimiter, this might still "validate" as CSV
        # but would have wrong structure
        reader = CSVReader(delimiter=",")
        # This should still return True as it's valid CSV (just single column)
        assert reader.validate_format(str(path)) is True

    def test_custom_quotechar(self, tmp_path: Path):
        """Test reading with custom quote character."""
        path = tmp_path / "quoted.csv"
        path.write_text("url,name\n'https://example.com/1.json','item1'\n")
        reader = CSVReader(quotechar="'")
        urls = reader.read_urls(str(path), "url")
        assert len(urls) == 1


class TestNDJSONReader:
    """Tests for NDJSONReader class."""

    @pytest.fixture
    def sample_ndjson_file(self, tmp_path: Path) -> Path:
        """Create a temporary NDJSON file with URLs."""
        path = tmp_path / "urls.ndjson"
        path.write_text(
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            '{"url": "https://example.com/item2.json", "id": "item2"}\n'
            '{"url": "https://example.com/item3.json", "id": "item3"}\n'
        )
        return path

    @pytest.fixture
    def sample_jsonl_file(self, tmp_path: Path) -> Path:
        """Create a temporary JSONL file with URLs."""
        path = tmp_path / "urls.jsonl"
        path.write_text(
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            '{"url": "https://example.com/item2.json", "id": "item2"}\n'
        )
        return path

    @pytest.fixture
    def ndjson_with_comments(self, tmp_path: Path) -> Path:
        """Create NDJSON file with comment lines."""
        path = tmp_path / "with_comments.ndjson"
        path.write_text(
            "# This is a comment\n"
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            "# Another comment\n"
            '{"url": "https://example.com/item2.json", "id": "item2"}\n'
        )
        return path

    @pytest.fixture
    def ndjson_with_empty_lines(self, tmp_path: Path) -> Path:
        """Create NDJSON file with empty lines."""
        path = tmp_path / "with_empty.ndjson"
        path.write_text(
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            "\n"
            "\n"
            '{"url": "https://example.com/item2.json", "id": "item2"}\n'
        )
        return path

    @pytest.fixture
    def ndjson_with_malformed(self, tmp_path: Path) -> Path:
        """Create NDJSON file with some malformed JSON."""
        path = tmp_path / "with_malformed.ndjson"
        path.write_text(
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            "this is not json\n"
            '{"url": "https://example.com/item2.json", "id": "item2"}\n'
            '{"url": "https://example.com/item3.json", "id": "item3"}\n'
        )
        return path

    @pytest.fixture
    def ndjson_custom_url_field(self, tmp_path: Path) -> Path:
        """Create NDJSON file with custom URL field name."""
        path = tmp_path / "custom_field.ndjson"
        path.write_text(
            '{"stac_url": "https://example.com/item1.json", "id": "item1"}\n'
            '{"stac_url": "https://example.com/item2.json", "id": "item2"}\n'
        )
        return path

    @pytest.fixture
    def ndjson_with_missing_field(self, tmp_path: Path) -> Path:
        """Create NDJSON file where some objects are missing the URL field."""
        path = tmp_path / "missing_field.ndjson"
        path.write_text(
            '{"url": "https://example.com/item1.json", "id": "item1"}\n'
            '{"id": "item2"}\n'  # Missing url field
            '{"url": "https://example.com/item3.json", "id": "item3"}\n'
        )
        return path

    @pytest.fixture
    def empty_ndjson_file(self, tmp_path: Path) -> Path:
        """Create an empty NDJSON file."""
        path = tmp_path / "empty.ndjson"
        path.write_text("")
        return path

    @pytest.fixture
    def ndjson_all_comments(self, tmp_path: Path) -> Path:
        """Create NDJSON file with only comments."""
        path = tmp_path / "all_comments.ndjson"
        path.write_text("# Comment 1\n# Comment 2\n# Comment 3\n")
        return path

    def test_read_urls_basic(self, sample_ndjson_file: Path):
        """Test reading URLs from NDJSON file."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(sample_ndjson_file), "url")
        assert len(urls) == 3
        assert "https://example.com/item1.json" in urls
        assert "https://example.com/item2.json" in urls
        assert "https://example.com/item3.json" in urls

    def test_read_urls_from_jsonl(self, sample_jsonl_file: Path):
        """Test reading URLs from JSONL file (.jsonl extension)."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(sample_jsonl_file), "url")
        assert len(urls) == 2
        assert "https://example.com/item1.json" in urls
        assert "https://example.com/item2.json" in urls

    def test_read_urls_with_comments(self, ndjson_with_comments: Path):
        """Test that comment lines are skipped."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(ndjson_with_comments), "url")
        assert len(urls) == 2
        assert "https://example.com/item1.json" in urls
        assert "https://example.com/item2.json" in urls

    def test_read_urls_with_empty_lines(self, ndjson_with_empty_lines: Path):
        """Test that empty lines are skipped."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(ndjson_with_empty_lines), "url")
        assert len(urls) == 2

    def test_read_urls_with_malformed_json(self, ndjson_with_malformed: Path):
        """Test that malformed JSON lines are skipped."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(ndjson_with_malformed), "url")
        # Should skip the malformed line
        assert len(urls) == 3
        assert "https://example.com/item1.json" in urls
        assert "https://example.com/item2.json" in urls
        assert "https://example.com/item3.json" in urls

    def test_read_urls_custom_field(self, ndjson_custom_url_field: Path):
        """Test reading from custom URL field name."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(ndjson_custom_url_field), "stac_url")
        assert len(urls) == 2
        assert urls[0] == "https://example.com/item1.json"

    def test_read_urls_missing_field(self, ndjson_with_missing_field: Path):
        """Test handling when some objects are missing the URL field."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(ndjson_with_missing_field), "url")
        # Should only get URLs from objects that have the field
        assert len(urls) == 2
        assert "https://example.com/item1.json" in urls
        assert "https://example.com/item3.json" in urls

    def test_read_urls_empty_file(self, empty_ndjson_file: Path):
        """Test reading from empty NDJSON file."""
        reader = NDJSONReader()
        urls = reader.read_urls(str(empty_ndjson_file), "url")
        assert len(urls) == 0
        assert isinstance(urls, list)

    def test_read_urls_all_comments(self, ndjson_all_comments: Path):
        """Test file with only comment lines."""
        reader = NDJSONReader()
        with pytest.raises(ValueError, match="does not contain"):
            reader.read_urls(str(ndjson_all_comments), "url")

    def test_read_urls_file_not_found(self):
        """Test error when file doesn't exist."""
        reader = NDJSONReader()
        with pytest.raises(FileNotFoundError):
            reader.read_urls("/nonexistent/path/to/file.ndjson", "url")

    def test_read_urls_empty_path(self):
        """Test error when file path is empty."""
        reader = NDJSONReader()
        with pytest.raises(ValueError, match="file_path cannot be empty"):
            reader.read_urls("", "url")

    def test_read_urls_all_missing_field(self, tmp_path: Path):
        """Test error when all objects are missing the URL field."""
        path = tmp_path / "all_missing.ndjson"
        path.write_text('{"id": "item1"}\n{"id": "item2"}\n')

        reader = NDJSONReader()
        with pytest.raises(ValueError, match="does not contain"):
            reader.read_urls(str(path), "url")

    def test_read_urls_preserves_order(self, tmp_path: Path):
        """Test that URL order is preserved."""
        urls = [f"https://example.com/item{i}.json" for i in range(100)]
        lines = [f'{{"url": "{url}", "id": "item{i}"}}\n' for i, url in enumerate(urls)]

        path = tmp_path / "ordered.ndjson"
        path.write_text("".join(lines))

        reader = NDJSONReader()
        result = reader.read_urls(str(path), "url")
        assert result == urls

    def test_read_urls_large_file(self, tmp_path: Path):
        """Test reading larger NDJSON files."""
        # Create file with 10000 lines
        lines = [f'{{"url": "https://example.com/item{i}.json"}}\n' for i in range(10000)]
        path = tmp_path / "large.ndjson"
        path.write_text("".join(lines))

        reader = NDJSONReader()
        result = reader.read_urls(str(path), "url")
        assert len(result) == 10000
        assert result[0] == "https://example.com/item0.json"
        assert result[-1] == "https://example.com/item9999.json"

    def test_validate_format_valid(self, sample_ndjson_file: Path):
        """Test format validation for valid NDJSON."""
        reader = NDJSONReader()
        assert reader.validate_format(str(sample_ndjson_file)) is True

    def test_validate_format_valid_jsonl(self, sample_jsonl_file: Path):
        """Test format validation for JSONL files."""
        reader = NDJSONReader()
        assert reader.validate_format(str(sample_jsonl_file)) is True

    def test_validate_format_invalid_json(self, tmp_path: Path):
        """Test format validation for invalid JSON."""
        invalid = tmp_path / "invalid.ndjson"
        invalid.write_text("this is not json\nnot json either\n")
        reader = NDJSONReader()
        assert reader.validate_format(str(invalid)) is False

    def test_validate_format_nonexistent(self):
        """Test format validation for nonexistent file."""
        reader = NDJSONReader()
        assert reader.validate_format("/nonexistent/file.ndjson") is False

    def test_validate_format_empty_file(self, empty_ndjson_file: Path):
        """Test format validation for empty file."""
        reader = NDJSONReader()
        # Empty file has no valid JSON, so should be False
        assert reader.validate_format(str(empty_ndjson_file)) is False

    def test_validate_format_with_comments(self, ndjson_with_comments: Path):
        """Test validation works with comment lines."""
        reader = NDJSONReader()
        # Should detect valid JSON after comments
        assert reader.validate_format(str(ndjson_with_comments)) is True


class TestReaderFactory:
    """Tests for ReaderFactory class."""

    @pytest.mark.parametrize(
        "format_name,expected_type,expected_delimiter",
        [
            ("parquet", ParquetReader, None),
            ("csv", CSVReader, ","),
            ("tsv", CSVReader, "\t"),
            ("ndjson", NDJSONReader, None),
            ("jsonl", NDJSONReader, None),
            ("PARQUET", ParquetReader, None),  # case insensitive
            ("NDJSON", NDJSONReader, None),  # case insensitive
            ("JSONL", NDJSONReader, None),  # case insensitive
        ],
        ids=["parquet", "csv", "tsv", "ndjson", "jsonl", "parquet_uppercase", "ndjson_uppercase", "jsonl_uppercase"],
    )
    def test_get_reader(self, format_name, expected_type, expected_delimiter):
        """Test getting reader for various format types."""
        reader = ReaderFactory.get_reader(format_name)
        assert isinstance(reader, expected_type)
        if expected_delimiter is not None:
            assert reader.delimiter == expected_delimiter

    def test_get_reader_unknown_format(self):
        """Test error for unknown format."""
        with pytest.raises(ValueError, match="Unsupported input format"):
            ReaderFactory.get_reader("unknown_format")

    @pytest.mark.parametrize(
        "file_path,expected_format",
        [
            ("data.parquet", "parquet"),
            ("s3://bucket/data.parquet", "parquet"),
            ("/path/to/DATA.PARQUET", "parquet"),
            ("data.csv", "csv"),
            ("s3://bucket/data.CSV", "csv"),
            ("data.tsv", "tsv"),
            ("data.ndjson", "ndjson"),
            ("s3://bucket/data.NDJSON", "ndjson"),
            ("data.jsonl", "jsonl"),
            ("/path/to/data.JSONL", "jsonl"),
            ("data.unknown", "csv"),  # defaults to csv
        ],
        ids=[
            "parquet",
            "parquet_s3",
            "parquet_upper",
            "csv",
            "csv_s3",
            "tsv",
            "ndjson",
            "ndjson_s3",
            "jsonl",
            "jsonl_upper",
            "unknown_defaults_csv",
        ],
    )
    def test_auto_detect_format(self, file_path, expected_format):
        """Test auto-detection of file formats."""
        assert ReaderFactory.auto_detect_format(file_path) == expected_format

    def test_get_supported_formats(self):
        """Test list of supported formats."""
        formats = ReaderFactory.get_supported_formats()
        assert "parquet" in formats
        assert "csv" in formats
        assert "tsv" in formats
        assert "ndjson" in formats
        assert "jsonl" in formats
        assert isinstance(formats, list)


class TestInputReaderInterface:
    """Tests for InputReader abstract interface compliance."""

    @pytest.mark.parametrize(
        "reader_class",
        [ParquetReader, CSVReader, NDJSONReader],
        ids=["parquet_reader", "csv_reader", "ndjson_reader"],
    )
    def test_reader_implements_interface(self, reader_class):
        """Test that readers implement InputReader interface."""
        reader = reader_class()
        assert isinstance(reader, InputReader)
        assert hasattr(reader, "read_urls")
        assert hasattr(reader, "validate_format")
        assert callable(reader.read_urls)
        assert callable(reader.validate_format)


class TestReaderIntegration:
    """Integration tests for reader workflows."""

    def test_factory_then_read_parquet(self, tmp_path: Path):
        """Test complete workflow: factory -> read parquet."""
        # Create test file
        df = pd.DataFrame({"stac_url": ["https://example.com/item.json"]})
        path = tmp_path / "test.parquet"
        df.to_parquet(path, index=False)

        # Use factory to get reader and read
        reader = ReaderFactory.get_reader("parquet")
        urls = reader.read_urls(str(path), "stac_url")
        assert urls == ["https://example.com/item.json"]

    def test_factory_then_read_csv(self, tmp_path: Path):
        """Test complete workflow: factory -> read csv."""
        path = tmp_path / "test.csv"
        path.write_text("stac_url\nhttps://example.com/item.json\n")

        reader = ReaderFactory.get_reader("csv")
        urls = reader.read_urls(str(path), "stac_url")
        assert urls == ["https://example.com/item.json"]

    def test_factory_then_read_ndjson(self, tmp_path: Path):
        """Test complete workflow: factory -> read ndjson."""
        path = tmp_path / "test.ndjson"
        path.write_text('{"stac_url": "https://example.com/item.json"}\n')

        reader = ReaderFactory.get_reader("ndjson")
        urls = reader.read_urls(str(path), "stac_url")
        assert urls == ["https://example.com/item.json"]

    def test_factory_auto_detect_ndjson(self, tmp_path: Path):
        """Test auto-detection workflow for ndjson files."""
        path = tmp_path / "data.jsonl"
        path.write_text('{"url": "https://example.com/item.json"}\n')

        # Auto-detect should pick jsonl format
        format_detected = ReaderFactory.auto_detect_format(str(path))
        assert format_detected == "jsonl"

        # Get reader and read
        reader = ReaderFactory.get_reader(format_detected)
        urls = reader.read_urls(str(path), "url")
        assert urls == ["https://example.com/item.json"]

    def test_large_file_handling(self, tmp_path: Path):
        """Test reading larger files works correctly."""
        # Create file with 10000 URLs
        urls = [f"https://example.com/item{i}.json" for i in range(10000)]
        df = pd.DataFrame({"url": urls})
        path = tmp_path / "large.parquet"
        df.to_parquet(path, index=False)

        reader = ParquetReader()
        result = reader.read_urls(str(path), "url")
        assert len(result) == 10000
        assert result[0] == "https://example.com/item0.json"
        assert result[-1] == "https://example.com/item9999.json"


class TestCloudStorageMocking:
    """Tests for cloud storage integration (mocked)."""

    @patch("earthcatalog.input_readers.HAS_FSSPEC", True)
    @patch("earthcatalog.input_readers.fsspec")
    def test_parquet_s3_path_detection(self, mock_fsspec):
        """Test that S3 paths trigger fsspec usage."""
        # Setup mock
        mock_fs = MagicMock()
        mock_fsspec.filesystem.return_value = mock_fs

        # Create a mock file object that returns parquet data
        mock_file = MagicMock()
        mock_fs.open.return_value.__enter__ = MagicMock(return_value=mock_file)
        mock_fs.open.return_value.__exit__ = MagicMock(return_value=False)

        reader = ParquetReader()

        # This should detect S3 and use fsspec
        try:
            reader.read_urls("s3://bucket/data.parquet", "url")
        except (ValueError, TypeError, OSError):
            # Expected to fail since mock doesn't return real data
            pass

        # Verify fsspec was called with s3
        mock_fsspec.filesystem.assert_called_once_with("s3")

    @patch("earthcatalog.input_readers.HAS_FSSPEC", True)
    @patch("earthcatalog.input_readers.fsspec")
    def test_csv_s3_path_detection(self, mock_fsspec):
        """Test that S3 paths trigger fsspec usage for CSV."""
        mock_fs = MagicMock()
        mock_fsspec.filesystem.return_value = mock_fs

        mock_file = MagicMock()
        mock_fs.open.return_value.__enter__ = MagicMock(return_value=mock_file)
        mock_fs.open.return_value.__exit__ = MagicMock(return_value=False)

        reader = CSVReader()

        try:
            reader.read_urls("s3://bucket/data.csv", "url")
        except (ValueError, TypeError, OSError):
            pass

        mock_fsspec.filesystem.assert_called_once_with("s3")

    @patch("earthcatalog.input_readers.HAS_FSSPEC", True)
    @patch("earthcatalog.input_readers.fsspec")
    def test_ndjson_s3_path_detection(self, mock_fsspec):
        """Test that S3 paths trigger fsspec usage for NDJSON."""
        mock_fs = MagicMock()
        mock_fsspec.filesystem.return_value = mock_fs

        mock_file = MagicMock()
        mock_fs.open.return_value.__enter__ = MagicMock(return_value=mock_file)
        mock_fs.open.return_value.__exit__ = MagicMock(return_value=False)

        reader = NDJSONReader()

        try:
            reader.read_urls("s3://bucket/data.ndjson", "url")
        except (ValueError, TypeError, OSError):
            pass

        mock_fsspec.filesystem.assert_called_once_with("s3")
