"""Tests for file storage implementation."""

import json
from pathlib import Path
from unittest.mock import patch


from local_deep_research.storage.file import FileReportStorage


class TestFileReportStorageInit:
    """Tests for FileReportStorage initialization."""

    def test_uses_default_directory_when_none(self, tmp_path):
        """Should use default research outputs directory when none provided."""
        with patch(
            "local_deep_research.storage.file.get_research_outputs_directory",
            return_value=tmp_path,
        ):
            storage = FileReportStorage()
            assert storage.base_dir == tmp_path

    def test_uses_provided_directory(self, tmp_path):
        """Should use provided directory."""
        custom_dir = tmp_path / "custom"
        storage = FileReportStorage(base_dir=custom_dir)
        assert storage.base_dir == custom_dir

    def test_creates_directory_if_not_exists(self, tmp_path):
        """Should create directory if it doesn't exist."""
        new_dir = tmp_path / "new_reports"
        storage = FileReportStorage(base_dir=new_dir)
        assert storage.base_dir.exists()


class TestGetReportPath:
    """Tests for _get_report_path method."""

    def test_returns_md_file_path(self, tmp_path):
        """Should return path with .md extension."""
        storage = FileReportStorage(base_dir=tmp_path)
        path = storage._get_report_path("test-uuid")
        assert path == tmp_path / "test-uuid.md"

    def test_returns_path_object(self, tmp_path):
        """Should return Path object."""
        storage = FileReportStorage(base_dir=tmp_path)
        path = storage._get_report_path("test-uuid")
        assert isinstance(path, Path)


class TestGetMetadataPath:
    """Tests for _get_metadata_path method."""

    def test_returns_metadata_json_path(self, tmp_path):
        """Should return path with _metadata.json suffix."""
        storage = FileReportStorage(base_dir=tmp_path)
        path = storage._get_metadata_path("test-uuid")
        assert path == tmp_path / "test-uuid_metadata.json"


class TestSaveReport:
    """Tests for save_report method."""

    def test_saves_report_content(self, tmp_path, sample_report_content):
        """Should save report content to file."""
        storage = FileReportStorage(base_dir=tmp_path)

        # Patch at source location since it's imported inside the method
        with patch(
            "local_deep_research.security.file_write_verifier.write_file_verified"
        ) as mock_write:
            with patch(
                "local_deep_research.security.file_write_verifier.write_json_verified"
            ):
                result = storage.save_report("test-uuid", sample_report_content)

        assert result is True
        mock_write.assert_called_once()

    def test_saves_metadata_when_provided(
        self, tmp_path, sample_report_content, sample_metadata
    ):
        """Should save metadata when provided."""
        storage = FileReportStorage(base_dir=tmp_path)

        with patch(
            "local_deep_research.security.file_write_verifier.write_file_verified"
        ):
            with patch(
                "local_deep_research.security.file_write_verifier.write_json_verified"
            ) as mock_json:
                storage.save_report(
                    "test-uuid", sample_report_content, metadata=sample_metadata
                )

        mock_json.assert_called_once()

    def test_skips_metadata_when_not_provided(
        self, tmp_path, sample_report_content
    ):
        """Should skip metadata save when not provided."""
        storage = FileReportStorage(base_dir=tmp_path)

        with patch(
            "local_deep_research.security.file_write_verifier.write_file_verified"
        ):
            with patch(
                "local_deep_research.security.file_write_verifier.write_json_verified"
            ) as mock_json:
                storage.save_report("test-uuid", sample_report_content)

        mock_json.assert_not_called()

    def test_returns_false_on_error(self, tmp_path, sample_report_content):
        """Should return False on error."""
        storage = FileReportStorage(base_dir=tmp_path)

        with patch(
            "local_deep_research.security.file_write_verifier.write_file_verified",
            side_effect=Exception("write error"),
        ):
            result = storage.save_report("test-uuid", sample_report_content)

        assert result is False


class TestGetReport:
    """Tests for get_report method."""

    def test_returns_content_when_file_exists(
        self, tmp_path, sample_report_content
    ):
        """Should return content when file exists."""
        storage = FileReportStorage(base_dir=tmp_path)
        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        result = storage.get_report("test-uuid")

        assert result == sample_report_content

    def test_returns_none_when_file_not_exists(self, tmp_path):
        """Should return None when file doesn't exist."""
        storage = FileReportStorage(base_dir=tmp_path)

        result = storage.get_report("nonexistent-uuid")

        assert result is None

    def test_returns_none_on_error(self, tmp_path):
        """Should return None on read error."""
        storage = FileReportStorage(base_dir=tmp_path)
        report_path = tmp_path / "test-uuid.md"
        report_path.write_text("content", encoding="utf-8")

        with patch("builtins.open", side_effect=Exception("read error")):
            result = storage.get_report("test-uuid")

        assert result is None


class TestGetReportWithMetadata:
    """Tests for get_report_with_metadata method."""

    def test_returns_content_and_metadata(
        self, tmp_path, sample_report_content, sample_metadata
    ):
        """Should return both content and metadata."""
        storage = FileReportStorage(base_dir=tmp_path)

        # Create files
        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        metadata_path = tmp_path / "test-uuid_metadata.json"
        metadata_path.write_text(json.dumps(sample_metadata), encoding="utf-8")

        result = storage.get_report_with_metadata("test-uuid")

        assert result["content"] == sample_report_content
        assert result["metadata"] == sample_metadata

    def test_returns_empty_metadata_when_missing(
        self, tmp_path, sample_report_content
    ):
        """Should return empty metadata when file missing."""
        storage = FileReportStorage(base_dir=tmp_path)

        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        result = storage.get_report_with_metadata("test-uuid")

        assert result["content"] == sample_report_content
        assert result["metadata"] == {}

    def test_returns_none_when_report_missing(self, tmp_path):
        """Should return None when report doesn't exist."""
        storage = FileReportStorage(base_dir=tmp_path)

        result = storage.get_report_with_metadata("nonexistent")

        assert result is None


class TestDeleteReport:
    """Tests for delete_report method."""

    def test_deletes_report_file(self, tmp_path, sample_report_content):
        """Should delete report file."""
        storage = FileReportStorage(base_dir=tmp_path)

        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        result = storage.delete_report("test-uuid")

        assert result is True
        assert not report_path.exists()

    def test_deletes_metadata_file(
        self, tmp_path, sample_report_content, sample_metadata
    ):
        """Should delete metadata file."""
        storage = FileReportStorage(base_dir=tmp_path)

        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        metadata_path = tmp_path / "test-uuid_metadata.json"
        metadata_path.write_text(json.dumps(sample_metadata), encoding="utf-8")

        storage.delete_report("test-uuid")

        assert not metadata_path.exists()

    def test_returns_false_when_files_not_exist(self, tmp_path):
        """Should return False when no files to delete."""
        storage = FileReportStorage(base_dir=tmp_path)

        result = storage.delete_report("nonexistent")

        assert result is False

    def test_returns_false_on_error(self, tmp_path, sample_report_content):
        """Should return False on delete error."""
        storage = FileReportStorage(base_dir=tmp_path)

        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        with patch.object(
            Path, "unlink", side_effect=Exception("delete error")
        ):
            result = storage.delete_report("test-uuid")

        assert result is False


class TestReportExists:
    """Tests for report_exists method."""

    def test_returns_true_when_file_exists(
        self, tmp_path, sample_report_content
    ):
        """Should return True when file exists."""
        storage = FileReportStorage(base_dir=tmp_path)

        report_path = tmp_path / "test-uuid.md"
        report_path.write_text(sample_report_content, encoding="utf-8")

        result = storage.report_exists("test-uuid")

        assert result is True

    def test_returns_false_when_file_not_exists(self, tmp_path):
        """Should return False when file doesn't exist."""
        storage = FileReportStorage(base_dir=tmp_path)

        result = storage.report_exists("nonexistent")

        assert result is False


class TestListReports:
    """Tests for list_reports method."""

    def test_returns_list_of_report_dicts(self, tmp_path):
        """Should return list of dicts for each .md file."""
        (tmp_path / "report-1.md").write_text("# Report 1", encoding="utf-8")
        (tmp_path / "report-2.md").write_text("# Report 2", encoding="utf-8")

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert len(result) == 2
        ids = {r["id"] for r in result}
        assert ids == {"report-1", "report-2"}
        for r in result:
            assert "id" in r
            assert "query" in r
            assert "mode" in r
            assert "created_at" in r
            assert "completed_at" in r

    def test_returns_empty_list_when_no_reports(self, tmp_path):
        """Should return empty list when directory has no .md files."""
        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert result == []

    def test_loads_metadata_from_json(self, tmp_path):
        """Should populate query and mode from metadata JSON when available."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")
        metadata = {"query": "test query", "mode": "detailed"}
        (tmp_path / "report-1_metadata.json").write_text(
            json.dumps(metadata), encoding="utf-8"
        )

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert len(result) == 1
        assert result[0]["query"] == "test query"
        assert result[0]["mode"] == "detailed"

    def test_handles_missing_metadata(self, tmp_path):
        """Should return None query and 'unknown' mode when no metadata file."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert len(result) == 1
        assert result[0]["query"] is None
        assert result[0]["mode"] == "unknown"

    def test_returns_empty_list_on_error(self, tmp_path):
        """Should return empty list on error."""
        storage = FileReportStorage(base_dir=tmp_path)

        with patch.object(Path, "glob", side_effect=Exception("glob error")):
            result = storage.list_reports()

        assert result == []

    def test_accepts_username_parameter(self, tmp_path):
        """Should accept username parameter without error."""
        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports(username="testuser")

        assert result == []

    def test_ignores_non_md_files(self, tmp_path):
        """Should only list .md files, ignoring other file types."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")
        (tmp_path / "report-1_metadata.json").write_text("{}", encoding="utf-8")
        (tmp_path / "notes.txt").write_text("notes", encoding="utf-8")
        (tmp_path / "data.csv").write_text("a,b", encoding="utf-8")

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert len(result) == 1
        assert result[0]["id"] == "report-1"

    def test_created_at_is_iso_timestamp(self, tmp_path):
        """Should return created_at as ISO 8601 string, not raw float."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        created_at = result[0]["created_at"]
        assert isinstance(created_at, str)
        # ISO format should contain 'T' separator and timezone info
        assert "T" in created_at

    def test_handles_corrupt_metadata_json(self, tmp_path):
        """Should gracefully handle corrupt metadata JSON."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")
        (tmp_path / "report-1_metadata.json").write_text(
            "not valid json{{{", encoding="utf-8"
        )

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert len(result) == 1
        assert result[0]["query"] is None
        assert result[0]["mode"] == "unknown"

    def test_metadata_with_extra_fields_only_uses_query_and_mode(
        self, tmp_path
    ):
        """Should extract only query and mode from metadata."""
        (tmp_path / "report-1.md").write_text("# Report", encoding="utf-8")
        metadata = {
            "query": "my query",
            "mode": "detailed",
            "extra_field": "ignored",
            "sources_count": 42,
        }
        (tmp_path / "report-1_metadata.json").write_text(
            json.dumps(metadata), encoding="utf-8"
        )

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage.list_reports()

        assert set(result[0].keys()) == {
            "id",
            "query",
            "mode",
            "created_at",
            "completed_at",
        }


class TestLoadMetadata:
    """Tests for _load_metadata helper method."""

    def test_returns_dict_when_metadata_exists(self, tmp_path):
        """Should return parsed dict from metadata JSON."""
        metadata = {"query": "test", "mode": "quick"}
        (tmp_path / "r1_metadata.json").write_text(
            json.dumps(metadata), encoding="utf-8"
        )

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage._load_metadata("r1")

        assert result == metadata

    def test_returns_none_when_no_metadata_file(self, tmp_path):
        """Should return None when metadata file doesn't exist."""
        storage = FileReportStorage(base_dir=tmp_path)
        result = storage._load_metadata("nonexistent")

        assert result is None

    def test_returns_none_on_invalid_json(self, tmp_path):
        """Should return None when metadata file has invalid JSON."""
        (tmp_path / "r1_metadata.json").write_text("{{bad", encoding="utf-8")

        storage = FileReportStorage(base_dir=tmp_path)
        result = storage._load_metadata("r1")

        assert result is None
