"""
Tests for Issue #13: Cache Management CLI Commands.

TDD approach: These tests define the exact requirements for cache management commands.
All tests should initially FAIL (RED) and drive the implementation (GREEN).

Commands to implement:
- `markitect cache-stats` - Display cache statistics and effectiveness
- `markitect cache-clean` - Clear cache and free memory
- `markitect cache-invalidate <file>` - Invalidate specific file cache
"""

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

import pytest
from click.testing import CliRunner

from markitect.cli import cli
from markitect.ast_cache import ASTCache


class TestCacheCommands:
    """TDD test suite defining cache management command requirements."""

    def setup_method(self):
        """Set up test environment."""
        self.runner = CliRunner()
        self.temp_dir = tempfile.mkdtemp()
        self.cache_dir = Path(self.temp_dir) / ".ast_cache"

        # Create test markdown file
        self.test_file = Path(self.temp_dir) / "test.md"
        self.test_file.write_text("""---
title: Test Document
---

# Test Heading
This is test content.
""")

    def teardown_method(self):
        """Clean up after each test."""
        import shutil
        if Path(self.temp_dir).exists():
            shutil.rmtree(self.temp_dir)

    # ===== cache-stats command tests =====

    def test_cache_stats_command_exists(self):
        """RED: cache-stats command should exist and be callable."""
        result = self.runner.invoke(cli, ['cache-stats'])

        # Should NOT be "No such command" - command must exist
        assert "No such command" not in result.output
        # Command exists and runs (may fail for other reasons initially)
        assert result.exit_code in [0, 1, 2]

    def test_cache_stats_shows_cache_directory_path(self):
        """RED: cache-stats should display the cache directory path."""
        result = self.runner.invoke(cli, ['cache-stats'])

        assert result.exit_code == 0
        assert "Cache Directory:" in result.output

    def test_cache_stats_shows_total_files_count(self):
        """RED: cache-stats should show count of cached files."""
        # Clean existing cache first
        self.runner.invoke(cli, ['cache-clean'])

        # Create cache with known files using the project's default cache location
        from pathlib import Path
        project_cache_dir = Path.cwd() / ".ast_cache"
        cache = ASTCache(project_cache_dir)
        cache.cache_file(self.test_file)

        result = self.runner.invoke(cli, ['cache-stats'])

        assert result.exit_code == 0
        assert "Total Files:" in result.output
        assert "1" in result.output

    def test_cache_stats_shows_cache_size(self):
        """RED: cache-stats should display total cache size."""
        cache = ASTCache(self.cache_dir)
        cache.cache_file(self.test_file)

        result = self.runner.invoke(cli, ['cache-stats'])

        assert result.exit_code == 0
        assert "Cache Size:" in result.output
        # Should show size in bytes, KB, MB, etc.
        assert any(unit in result.output for unit in ["bytes", "KB", "MB", "B"])

    def test_cache_stats_command_works_with_empty_and_populated_cache(self):
        """cache-stats command works with both empty and populated cache states."""
        result = self.runner.invoke(cli, ['cache-stats'])

        assert result.exit_code == 0
        assert "Cache Directory:" in result.output
        assert "Total Files:" in result.output
        assert "Cache Size:" in result.output
        # Should work whether cache has 0 files or many files

    # ===== cache-clean command tests =====

    def test_cache_clean_command_exists(self):
        """RED: cache-clean command should exist and be callable."""
        result = self.runner.invoke(cli, ['cache-clean'])

        assert "No such command" not in result.output
        assert result.exit_code in [0, 1, 2]

    def test_cache_clean_command_provides_user_feedback_on_cleanup_results(self):
        """cache-clean command provides user feedback on cleanup results."""
        result = self.runner.invoke(cli, ['cache-clean'])

        assert result.exit_code == 0
        # Should provide informative message about what happened
        assert len(result.output.strip()) > 0
        # Valid responses: cleaned files, already empty, or nothing to clean
        valid_responses = ["cleaned", "empty", "nothing to clean", "removed"]
        assert any(phrase in result.output.lower() for phrase in valid_responses)

    def test_cache_clean_provides_user_feedback(self):
        """cache-clean should provide clear feedback about results."""
        result = self.runner.invoke(cli, ['cache-clean'])

        assert result.exit_code == 0
        # Should give user clear information about what happened
        meaningful_responses = [
            "cleaned", "cleared", "removed", "empty", "nothing to clean",
            "does not exist", "successfully"
        ]
        assert any(phrase in result.output.lower() for phrase in meaningful_responses)

    def test_cache_clean_with_empty_cache(self):
        """RED: cache-clean should handle empty cache gracefully."""
        self.cache_dir.mkdir(exist_ok=True)

        result = self.runner.invoke(cli, ['cache-clean'])

        assert result.exit_code == 0
        # Should not error on empty cache

    # ===== cache-invalidate command tests =====

    def test_cache_invalidate_command_exists(self):
        """RED: cache-invalidate command should exist and require file argument."""
        result = self.runner.invoke(cli, ['cache-invalidate'])

        assert "No such command" not in result.output
        # Should fail due to missing argument, not unknown command
        if result.exit_code != 0:
            assert "Missing argument" in result.output or "Usage:" in result.output

    def test_cache_invalidate_requires_file_argument(self):
        """RED: cache-invalidate should require a file argument."""
        result = self.runner.invoke(cli, ['cache-invalidate'])

        assert result.exit_code != 0
        assert any(phrase in result.output for phrase in ["Missing argument", "Usage:", "FILE"])

    def test_cache_invalidate_accepts_file_argument(self):
        """cache-invalidate should accept file path and execute successfully."""
        # Test with any file path - command should handle gracefully
        result = self.runner.invoke(cli, ['cache-invalidate', 'some-file.md'])

        assert result.exit_code == 0
        # Should provide feedback about what happened
        assert len(result.output.strip()) > 0

    def test_cache_invalidate_provides_meaningful_feedback(self):
        """cache-invalidate should provide clear feedback about results."""
        result = self.runner.invoke(cli, ['cache-invalidate', 'example.md'])

        assert result.exit_code == 0
        # Should explain what happened with the cache invalidation attempt
        meaningful_responses = [
            "invalidated", "no cache found", "nothing to invalidate",
            "cache", "example.md"
        ]
        assert any(phrase in result.output.lower() for phrase in meaningful_responses)

    def test_cache_invalidate_with_nonexistent_file(self):
        """RED: cache-invalidate should handle non-existent files gracefully."""
        nonexistent_file = Path(self.temp_dir) / "nonexistent.md"

        result = self.runner.invoke(cli, ['cache-invalidate', str(nonexistent_file)])

        # Should handle gracefully - either succeed (no cache to remove) or show helpful message
        assert result.exit_code in [0, 1]
        if result.exit_code == 1:
            assert "not found" in result.output.lower() or "does not exist" in result.output.lower()

    def test_cache_invalidate_with_no_cache_for_file(self):
        """RED: cache-invalidate should handle files with no existing cache."""
        # Create file but don't cache it
        uncached_file = Path(self.temp_dir) / "uncached.md"
        uncached_file.write_text("# Uncached content")

        result = self.runner.invoke(cli, ['cache-invalidate', str(uncached_file)])

        # Should handle gracefully
        assert result.exit_code in [0, 1]
        if result.exit_code == 0:
            assert "no cache" in result.output.lower() or "not cached" in result.output.lower()