"""
Test CLI integration for Issue #139: Implode directory to a markdown file.

This test module covers the md-implode command integration with the existing
markdown plugin system and CLI infrastructure.
"""

import pytest
import tempfile
import shutil
import subprocess
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from click.testing import CliRunner

# Import will fail initially (RED phase) until implementation exists
try:
    from markitect.plugins.builtin.markdown_commands import (
        md_implode_command,
        cli_implode_directory,
        ImplodeOptions,
        validate_implode_arguments
    )
    from markitect.cli import cli
except ImportError:
    # Expected during RED phase - tests should fail initially
    md_implode_command = None
    cli_implode_directory = None
    ImplodeOptions = None
    validate_implode_arguments = None
    cli = None


class TestImplodeCommandCLI:
    """Test the md-implode CLI command functionality."""

    def setup_method(self):
        """Set up temporary directory for each test."""
        self.temp_dir = Path(tempfile.mkdtemp())
        self.runner = CliRunner()

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

    def test_implode_command_exists_and_accessible(self):
        """Test that md-implode command exists and is accessible."""
        # This should fail initially (RED phase)

        # Test command registration
        assert md_implode_command is not None

        # Test CLI integration - should be available
        result = self.runner.invoke(cli, ['--help'])
        assert result.exit_code == 0
        assert 'md-implode' in result.output or 'implode' in result.output

    def test_implode_command_help_text(self):
        """Test that command provides proper help text and examples."""
        # This should fail initially (RED phase)

        result = self.runner.invoke(cli, ['md-implode', '--help'])

        assert result.exit_code == 0
        assert 'Implode a directory structure' in result.output
        assert 'markdown file' in result.output
        assert 'INPUT_DIR' in result.output or 'directory' in result.output

        # Should include usage examples
        assert 'Example' in result.output or 'Usage' in result.output

    def test_implode_command_accepts_input_directory(self):
        """Test command accepts input directory parameter."""
        # This should fail initially (RED phase)

        # Create test structure
        test_dir = self.temp_dir / "test_structure"
        test_dir.mkdir()
        (test_dir / "chapter1.md").write_text("# Chapter 1\nContent")

        result = self.runner.invoke(cli, ['md-implode', str(test_dir)])

        # Should accept directory parameter without error
        # May fail for other reasons during RED phase, but should recognize parameter
        assert 'No such option' not in result.output
        assert 'Invalid value' not in result.output

    def test_implode_command_supports_output_file_option(self):
        """Test command supports output file option."""
        # This should fail initially (RED phase)

        test_dir = self.temp_dir / "test_structure"
        test_dir.mkdir()
        (test_dir / "content.md").write_text("# Content\nSome content")

        output_file = self.temp_dir / "output.md"

        result = self.runner.invoke(cli, [
            'md-implode', str(test_dir),
            '--output', str(output_file)
        ])

        # Should accept output option
        assert 'No such option: --output' not in result.output

    def test_implode_command_dry_run_option(self):
        """Test command supports dry-run option."""
        # This should fail initially (RED phase)

        test_dir = self.temp_dir / "test_structure"
        test_dir.mkdir()
        (test_dir / "content.md").write_text("# Content\nSome content")

        result = self.runner.invoke(cli, [
            'md-implode', str(test_dir), '--dry-run'
        ])

        # Should support dry-run without error
        assert 'No such option: --dry-run' not in result.output

    def test_implode_command_verbose_option(self):
        """Test command supports verbose output option."""
        # This should fail initially (RED phase)

        test_dir = self.temp_dir / "test_structure"
        test_dir.mkdir()
        (test_dir / "content.md").write_text("# Content\nSome content")

        result = self.runner.invoke(cli, [
            'md-implode', str(test_dir), '--verbose'
        ])

        # Should support verbose option
        assert 'No such option: --verbose' not in result.output

    def test_implode_command_validates_input_directory(self):
        """Test command validates that input directory exists."""
        # This should fail initially (RED phase)

        nonexistent_dir = self.temp_dir / "nonexistent"

        result = self.runner.invoke(cli, ['md-implode', str(nonexistent_dir)])

        # Should provide appropriate error for non-existent directory
        assert result.exit_code != 0
        assert 'not found' in result.output.lower() or 'does not exist' in result.output.lower()

    def test_implode_command_validates_directory_has_markdown(self):
        """Test command validates that directory contains markdown files."""
        # This should fail initially (RED phase)

        empty_dir = self.temp_dir / "empty"
        empty_dir.mkdir()

        result = self.runner.invoke(cli, ['md-implode', str(empty_dir)])

        # Should handle empty directory appropriately
        assert result.exit_code != 0 or 'no markdown files' in result.output.lower()


class TestImplodeOptionsClass:
    """Test the ImplodeOptions configuration class."""

    def setup_method(self):
        """Set up temporary directory for each test."""
        self.temp_dir = Path(tempfile.mkdtemp())

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

    def test_implode_options_creation(self):
        """Test creating ImplodeOptions instances."""
        # This should fail initially (RED phase)

        options = ImplodeOptions()

        assert options is not None
        assert hasattr(options, 'input_dir')
        assert hasattr(options, 'output_file')
        assert hasattr(options, 'dry_run')
        assert hasattr(options, 'verbose')

    def test_implode_options_with_parameters(self):
        """Test creating ImplodeOptions with specific parameters."""
        # This should fail initially (RED phase)

        options = ImplodeOptions(
            input_dir=Path("/test/dir"),
            output_file=Path("/test/output.md"),
            dry_run=True,
            verbose=True,
            preserve_front_matter=True,
            section_spacing=2
        )

        assert options.input_dir == Path("/test/dir")
        assert options.output_file == Path("/test/output.md")
        assert options.dry_run == True
        assert options.verbose == True
        assert options.preserve_front_matter == True
        assert options.section_spacing == 2

    def test_implode_options_validation(self):
        """Test validation of ImplodeOptions parameters."""
        # This should fail initially (RED phase)

        # Create existing directory for valid test
        existing_dir = self.temp_dir / "valid_input"
        existing_dir.mkdir()

        # Valid options should pass
        valid_options = ImplodeOptions(
            input_dir=existing_dir,
            output_file=self.temp_dir / "output.md"
        )

        validation_result = validate_implode_arguments(valid_options)
        assert validation_result.is_valid == True

        # Invalid options should fail validation
        invalid_options = ImplodeOptions(
            input_dir=None,
            output_file=Path("/invalid/path")
        )

        validation_result = validate_implode_arguments(invalid_options)
        assert validation_result.is_valid == False
        assert len(validation_result.errors) > 0


class TestCLIImplodeFunction:
    """Test the core CLI implode function."""

    def setup_method(self):
        """Set up temporary directory for each test."""
        self.temp_dir = Path(tempfile.mkdtemp())

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

    def test_cli_implode_directory_basic_usage(self):
        """Test basic directory implosion functionality."""
        # This should fail initially (RED phase)

        # Create test structure
        (self.temp_dir / "intro.md").write_text("# Introduction\nIntro content")
        (self.temp_dir / "chapter1.md").write_text("## Chapter 1\nChapter content")

        output_file = self.temp_dir / "combined.md"

        result = cli_implode_directory(
            input_dir=self.temp_dir,
            output_file=output_file
        )

        # Should complete successfully
        assert result.success == True
        assert output_file.exists()

        # Check output content
        content = output_file.read_text()
        assert "# Introduction" in content
        assert "## Chapter 1" in content

    def test_cli_implode_directory_with_nested_structure(self):
        """Test implosion of nested directory structures."""
        # This should fail initially (RED phase)

        # Create nested structure
        part_dir = self.temp_dir / "part_1_introduction"
        part_dir.mkdir()
        (part_dir / "index.md").write_text("# Part 1: Introduction\nPart content")

        chapter_dir = part_dir / "chapter_1_overview"
        chapter_dir.mkdir()
        (chapter_dir / "index.md").write_text("## Chapter 1: Overview\nChapter content")
        (chapter_dir / "section_1.md").write_text("### Section 1\nSection content")

        output_file = self.temp_dir / "imploded.md"

        result = cli_implode_directory(
            input_dir=self.temp_dir,
            output_file=output_file
        )

        assert result.success == True

        content = output_file.read_text()

        # Should reconstruct hierarchy properly
        assert "# Part 1: Introduction" in content
        assert "## Chapter 1: Overview" in content
        assert "### Section 1" in content

    def test_cli_implode_directory_dry_run_mode(self):
        """Test dry run mode that previews without creating files."""
        # This should fail initially (RED phase)

        (self.temp_dir / "content.md").write_text("# Content\nSome content")
        output_file = self.temp_dir / "output.md"

        result = cli_implode_directory(
            input_dir=self.temp_dir,
            output_file=output_file,
            dry_run=True
        )

        # Should report what would be done
        assert result.success == True
        assert result.preview is not None

        # Should not create actual file
        assert not output_file.exists()

        # Preview should contain expected content structure
        assert "# Content" in result.preview

    def test_cli_implode_directory_verbose_output(self):
        """Test verbose mode provides detailed processing information."""
        # This should fail initially (RED phase)

        (self.temp_dir / "test.md").write_text("# Test\nTest content")
        output_file = self.temp_dir / "output.md"

        result = cli_implode_directory(
            input_dir=self.temp_dir,
            output_file=output_file,
            verbose=True
        )

        # Should provide detailed information
        assert result.success == True
        assert result.processing_info is not None
        assert len(result.processing_info) > 0

        # Processing info should include useful details
        info = result.processing_info
        assert any("found" in item.lower() and "files" in item.lower() for item in info)
        assert any("directory" in item.lower() for item in info)

    def test_cli_implode_handles_file_conflicts(self):
        """Test handling of output file conflicts."""
        # This should fail initially (RED phase)

        (self.temp_dir / "source.md").write_text("# Source\nSource content")
        output_file = self.temp_dir / "existing.md"
        output_file.write_text("# Existing\nExisting content")

        # Should handle existing file appropriately
        result = cli_implode_directory(
            input_dir=self.temp_dir,
            output_file=output_file,
            overwrite=True
        )

        assert result.success == True

        # Should overwrite with new content
        content = output_file.read_text()
        assert "# Source" in content
        assert "# Existing" not in content

    def test_cli_implode_handles_errors_gracefully(self):
        """Test graceful error handling for various failure scenarios."""
        # This should fail initially (RED phase)

        # Test with permission error scenario
        readonly_dir = self.temp_dir / "readonly"
        readonly_dir.mkdir()
        (readonly_dir / "content.md").write_text("# Content")

        # Try to write to a location that should fail
        protected_output = Path("/root/protected.md")

        result = cli_implode_directory(
            input_dir=readonly_dir,
            output_file=protected_output
        )

        # Should handle error gracefully
        assert result.success == False
        assert result.error_message is not None
        assert len(result.error_message) > 0


class TestMarkdownPluginIntegration:
    """Test integration with the existing markdown plugin system."""

    def test_implode_command_registered_in_plugin(self):
        """Test that implode command is properly registered in markdown plugin."""
        # This should fail initially (RED phase)

        # Should be able to import and access command
        assert md_implode_command is not None

        # Command should have proper Click decorators and setup
        assert hasattr(md_implode_command, 'callback')
        assert hasattr(md_implode_command, 'params')

    def test_implode_integrates_with_existing_commands(self):
        """Test that implode command integrates well with existing md-* commands."""
        # This should fail initially (RED phase)

        runner = CliRunner()

        # Should be listed alongside other markdown commands
        result = runner.invoke(cli, ['--help'])
        assert result.exit_code == 0

        # Should see both explode and implode commands
        help_output = result.output.lower()
        assert 'explode' in help_output
        assert 'implode' in help_output

    def test_implode_command_follows_plugin_conventions(self):
        """Test that implode command follows established plugin conventions."""
        # This should fail initially (RED phase)

        # Should use similar parameter patterns as other commands
        runner = CliRunner()

        explode_help = runner.invoke(cli, ['md-explode', '--help'])
        implode_help = runner.invoke(cli, ['md-implode', '--help'])

        assert explode_help.exit_code == 0
        assert implode_help.exit_code == 0

        # Should have similar option patterns
        explode_options = explode_help.output.lower()
        implode_options = implode_help.output.lower()

        # Both should support common options like verbose, dry-run
        if '--verbose' in explode_options:
            assert '--verbose' in implode_options
        if '--dry-run' in explode_options:
            assert '--dry-run' in implode_options

    @patch('markitect.plugins.builtin.markdown_commands.cli_implode_directory')
    def test_implode_command_calls_core_function(self, mock_implode_func):
        """Test that CLI command properly calls core implode function."""
        # This should fail initially (RED phase)

        mock_implode_func.return_value = Mock(success=True, output_file=Path("/test/output.md"))

        runner = CliRunner()

        with runner.isolated_filesystem():
            # Create test directory
            test_dir = Path("test_dir")
            test_dir.mkdir()
            (test_dir / "content.md").write_text("# Content")

            result = runner.invoke(cli, ['md-implode', str(test_dir)])

            # Should call the core function
            mock_implode_func.assert_called_once()

            # Should pass correct parameters
            call_args = mock_implode_func.call_args
            assert call_args is not None