"""Comprehensive testing for CLI output formats and error conditions."""

import os
import tempfile
import json
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from io import StringIO

import pytest
from typer.testing import CliRunner
from rich.console import Console

from ai_trackdown_pytools.cli import app


@pytest.fixture
def output_runner():
    """Create a CLI runner for output format testing."""
    return CliRunner()


@pytest.fixture
def mock_project_for_output():
    """Mock project setup for output testing."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_path = Path(temp_dir) / "output_test_project"
        project_path.mkdir()

        original_cwd = os.getcwd()
        os.chdir(str(project_path))

        try:
            yield project_path
        finally:
            os.chdir(original_cwd)


class TestJSONOutputFormat:
    """Test JSON output format for all commands."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_search_json_output(self, mock_task_mgr, mock_exists, output_runner):
        """Test JSON output format for search command."""
        mock_exists.return_value = True

        # Mock task data
        mock_tasks = [
            Mock(
                id="TSK-001",
                title="Task 1",
                description="Description 1",
                tags=["test"],
                status="open",
                priority="high",
                assignees=["alice"],
                created_at="2024-01-01T10:00:00",
                updated_at="2024-01-01T10:00:00",
            ),
            Mock(
                id="TSK-002",
                title="Task 2",
                description="Description 2",
                tags=["feature"],
                status="completed",
                priority="medium",
                assignees=["bob"],
                created_at="2024-01-02T10:00:00",
                updated_at="2024-01-02T15:00:00",
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Mock JSON serialization
        with patch("json.dumps") as mock_json:
            mock_json.return_value = json.dumps(
                [
                    {
                        "id": "TSK-001",
                        "title": "Task 1",
                        "description": "Description 1",
                        "tags": ["test"],
                        "status": "open",
                        "priority": "high",
                        "assignees": ["alice"],
                    },
                    {
                        "id": "TSK-002",
                        "title": "Task 2",
                        "description": "Description 2",
                        "tags": ["feature"],
                        "status": "completed",
                        "priority": "medium",
                        "assignees": ["bob"],
                    },
                ]
            )

            result = output_runner.invoke(app, ["search", "task", "--output", "json"])
            if "--output" in str(result.output):  # If JSON output is supported
                assert result.exit_code == 0
                # Verify JSON structure
                try:
                    output_data = json.loads(result.output)
                    assert isinstance(output_data, list)
                    assert len(output_data) >= 0
                except json.JSONDecodeError:
                    # JSON output might not be implemented yet
                    pass

    @patch("ai_trackdown_pytools.utils.system.get_system_info")
    def test_info_json_output(self, mock_system_info, output_runner):
        """Test JSON output format for info command."""
        mock_system_info.return_value = {
            "python_version": "3.9.0",
            "platform": "Linux",
            "architecture": "x86_64",
            "cwd": "/test",
            "git_repo": "Yes",
            "config_file": "/test/config.yaml",
            "templates_dir": "/test/templates",
            "schema_dir": "/test/schemas",
        }

        result = output_runner.invoke(app, ["info", "--output", "json"])
        # JSON output option might not exist yet
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.utils.validation.validate_project_structure")
    def test_validate_json_output(self, mock_validate, mock_exists, output_runner):
        """Test JSON output format for validation results."""
        mock_exists.return_value = True
        mock_validate.return_value = {
            "valid": False,
            "errors": ["Missing tasks directory", "Invalid config format"],
            "warnings": ["Empty templates directory"],
            "timestamp": "2024-01-01T10:00:00Z",
            "project_path": "/test/project",
        }

        result = output_runner.invoke(app, ["validate", "project", "--output", "json"])
        assert result.exit_code == 0

        # Try to parse JSON if output format is supported
        try:
            if result.output.strip().startswith("{"):
                validation_data = json.loads(result.output)
                assert "valid" in validation_data
                assert "errors" in validation_data
        except json.JSONDecodeError:
            # JSON output might not be implemented
            pass


class TestCSVOutputFormat:
    """Test CSV output format for tabular data."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_search_csv_output(self, mock_task_mgr, mock_exists, output_runner):
        """Test CSV output format for search results."""
        mock_exists.return_value = True

        # Mock task data for CSV export
        mock_tasks = [
            Mock(
                id="TSK-001",
                title="Task 1",
                description="First task",
                tags=["test", "urgent"],
                status="open",
                priority="high",
                assignees=["alice"],
            ),
            Mock(
                id="TSK-002",
                title="Task with, comma",
                description="Second task",
                tags=["feature"],
                status="in_progress",
                priority="medium",
                assignees=["bob", "charlie"],
            ),
            Mock(
                id="TSK-003",
                title='Task with "quotes"',
                description="Third task",
                tags=[],
                status="completed",
                priority="low",
                assignees=[],
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        result = output_runner.invoke(app, ["search", "task", "--output", "csv"])
        assert result.exit_code == 0

        # Check CSV format if supported
        if "TSK-001" in result.output and "," in result.output:
            lines = result.output.strip().split("\n")
            assert len(lines) >= 2  # Header + at least one row
            # Check for proper CSV escaping of commas and quotes

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_status_csv_export(self, mock_task_mgr, mock_exists, output_runner):
        """Test CSV export for status reports."""
        mock_exists.return_value = True

        # Mock status data
        mock_tasks = [
            Mock(id="TSK-001", status="open", priority="high", created_at="2024-01-01"),
            Mock(
                id="TSK-002",
                status="completed",
                priority="medium",
                created_at="2024-01-02",
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        result = output_runner.invoke(app, ["status", "tasks", "--export", "csv"])
        # Export option might not exist yet
        assert result.exit_code == 0


class TestXMLOutputFormat:
    """Test XML output format."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_search_xml_output(self, mock_task_mgr, mock_exists, output_runner):
        """Test XML output format for search results."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-001",
                title="XML Test Task",
                description="Task for XML testing",
                tags=["xml", "test"],
                status="open",
                priority="medium",
            )
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        result = output_runner.invoke(app, ["search", "xml", "--output", "xml"])
        assert result.exit_code == 0

        # Check basic XML structure if supported
        if result.output.strip().startswith(
            "<?xml"
        ) or result.output.strip().startswith("<"):
            assert "<tasks>" in result.output or "<results>" in result.output

    @patch("ai_trackdown_pytools.utils.system.get_system_info")
    def test_info_xml_output(self, mock_system_info, output_runner):
        """Test XML output for system info."""
        mock_system_info.return_value = {
            "python_version": "3.9.0",
            "platform": "Linux",
            "git_repo": "Yes",
        }

        result = output_runner.invoke(app, ["info", "--output", "xml"])
        assert result.exit_code == 0


class TestYAMLOutputFormat:
    """Test YAML output format."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_search_yaml_output(self, mock_task_mgr, mock_exists, output_runner):
        """Test YAML output format."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-001",
                title="YAML Test",
                description="Testing YAML output",
                tags=["yaml"],
                status="open",
                priority="low",
            )
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        result = output_runner.invoke(app, ["search", "yaml", "--output", "yaml"])
        assert result.exit_code == 0

        # Check YAML format indicators
        if ":" in result.output and ("- " in result.output or "id:" in result.output):
            # Basic YAML structure detected
            pass


class TestTableFormattingOptions:
    """Test various table formatting options."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_table_styles(self, mock_task_mgr, mock_exists, output_runner):
        """Test different table styles."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(id="TSK-001", title="Style Test 1", status="open", priority="high"),
            Mock(
                id="TSK-002", title="Style Test 2", status="completed", priority="low"
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Test various table styles
        table_styles = ["ascii", "simple", "grid", "rounded", "heavy", "double"]

        for style in table_styles:
            result = output_runner.invoke(
                app, ["search", "style", "--table-style", style]
            )
            assert result.exit_code == 0
            # Table style option might not exist yet

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_column_customization(self, mock_task_mgr, mock_exists, output_runner):
        """Test custom column selection."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-001",
                title="Column Test",
                status="open",
                priority="medium",
                assignees=["alice"],
                created_at="2024-01-01",
                tags=["test"],
            )
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Test custom column selection
        result = output_runner.invoke(
            app, ["search", "column", "--columns", "id,title,status,priority"]
        )
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_pagination_options(self, mock_task_mgr, mock_exists, output_runner):
        """Test table pagination options."""
        mock_exists.return_value = True

        # Mock large dataset
        mock_tasks = [
            Mock(
                id=f"TSK-{i:03d}",
                title=f"Pagination Test {i}",
                status="open",
                priority="medium",
            )
            for i in range(1, 51)
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Test pagination
        result = output_runner.invoke(
            app, ["search", "pagination", "--page-size", "10", "--page", "1"]
        )
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_sorting_options(self, mock_task_mgr, mock_exists, output_runner):
        """Test table sorting options."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-003",
                title="C Task",
                status="open",
                priority="low",
                created_at="2024-01-03",
            ),
            Mock(
                id="TSK-001",
                title="A Task",
                status="completed",
                priority="high",
                created_at="2024-01-01",
            ),
            Mock(
                id="TSK-002",
                title="B Task",
                status="in_progress",
                priority="medium",
                created_at="2024-01-02",
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Test different sorting options
        sort_options = [
            ("id", "asc"),
            ("title", "desc"),
            ("status", "asc"),
            ("priority", "desc"),
            ("created_at", "asc"),
        ]

        for field, order in sort_options:
            result = output_runner.invoke(
                app, ["search", "sort", "--sort-by", field, "--sort-order", order]
            )
            assert result.exit_code == 0


class TestErrorConditions:
    """Test various error conditions and their handling."""

    def test_invalid_command(self, output_runner):
        """Test handling of invalid commands."""
        result = output_runner.invoke(app, ["nonexistent-command"])
        assert result.exit_code != 0
        assert "No such command" in result.output or "Usage:" in result.output

    def test_invalid_option(self, output_runner):
        """Test handling of invalid options."""
        result = output_runner.invoke(app, ["info", "--nonexistent-option"])
        assert result.exit_code != 0
        assert "No such option" in result.output or "Usage:" in result.output

    def test_missing_required_argument(self, output_runner):
        """Test handling of missing required arguments."""
        result = output_runner.invoke(app, ["edit"])  # Missing task ID
        assert result.exit_code != 0

    def test_invalid_project_directory(self, output_runner):
        """Test handling of invalid project directory."""
        result = output_runner.invoke(
            app, ["--project-dir", "/nonexistent/path", "info"]
        )
        assert result.exit_code == 1
        assert "Cannot access project directory" in result.output

    def test_no_project_found(self, output_runner):
        """Test commands when no project is found."""
        with tempfile.TemporaryDirectory() as temp_dir:
            os.chdir(temp_dir)

            result = output_runner.invoke(app, ["task", "create", "Test Task"])
            assert result.exit_code == 1
            assert "No AI Trackdown project found" in result.output

    def test_permission_errors(self, output_runner):
        """Test handling of permission errors."""
        with patch("builtins.open", side_effect=PermissionError("Permission denied")):
            result = output_runner.invoke(app, ["config", "test_key", "test_value"])
            # Should handle permission error gracefully
            # Exact behavior depends on implementation

    def test_file_not_found_errors(self, output_runner):
        """Test handling of file not found errors."""
        result = output_runner.invoke(
            app, ["--config", "/nonexistent/config.yaml", "info"]
        )
        # Should handle missing config file gracefully
        assert result.exit_code in [0, 1]  # Might load defaults or fail

    def test_corrupted_config_file(self, output_runner):
        """Test handling of corrupted configuration files."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write("invalid: yaml: content: [unclosed bracket")
            config_path = f.name

        try:
            result = output_runner.invoke(app, ["--config", config_path, "info"])
            # Should handle YAML parsing errors gracefully
            assert result.exit_code in [0, 1]
        finally:
            os.unlink(config_path)

    def test_network_timeout_errors(self, output_runner, mock_project_for_output):
        """Test handling of network timeout errors."""
        with patch("requests.get", side_effect=TimeoutError("Request timeout")):
            result = output_runner.invoke(app, ["sync", "import", "github"])
            # Should handle network timeouts gracefully
            # Command might not exist yet

    def test_disk_space_errors(self, output_runner, mock_project_for_output):
        """Test handling of disk space errors."""
        with patch("builtins.open", side_effect=OSError("No space left on device")):
            result = output_runner.invoke(app, ["task", "create", "Space Test"])
            # Should handle disk space errors

    def test_invalid_json_config(self, output_runner):
        """Test handling of invalid JSON in configuration."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            f.write('{"invalid": json content}')
            config_path = f.name

        try:
            result = output_runner.invoke(app, ["--config", config_path, "info"])
            # Should handle JSON parsing errors
            assert result.exit_code in [0, 1]
        finally:
            os.unlink(config_path)


class TestValidationErrors:
    """Test validation error handling."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_invalid_priority_values(self, mock_create, mock_exists, output_runner):
        """Test handling of invalid priority values."""
        mock_exists.return_value = True

        result = output_runner.invoke(
            app, ["task", "create", "Invalid Priority Task", "--priority", "invalid"]
        )
        # Typer should handle choice validation automatically
        assert result.exit_code != 0 or "invalid" not in result.output.lower()

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_invalid_status_values(self, mock_create, mock_exists, output_runner):
        """Test handling of invalid status values."""
        mock_exists.return_value = True

        result = output_runner.invoke(
            app, ["task", "update", "TSK-001", "--status", "invalid_status"]
        )
        # Should validate status values

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_empty_required_fields(self, mock_create, mock_exists, output_runner):
        """Test handling of empty required fields."""
        mock_exists.return_value = True

        result = output_runner.invoke(app, ["task", "create", ""])  # Empty title
        # Should validate required fields
        assert result.exit_code != 0 or len(result.output) > 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    def test_invalid_task_id_format(self, mock_exists, output_runner):
        """Test handling of invalid task ID formats."""
        mock_exists.return_value = True

        result = output_runner.invoke(app, ["edit", "invalid-task-id-format"])
        assert result.exit_code == 1
        assert "not found" in result.output.lower()

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_circular_dependency_detection(
        self, mock_task_mgr, mock_exists, output_runner
    ):
        """Test detection of circular dependencies."""
        mock_exists.return_value = True

        # Mock creating a task that would create circular dependency
        mock_task_mgr.return_value.create_task.side_effect = ValueError(
            "Circular dependency detected"
        )

        result = output_runner.invoke(
            app,
            [
                "task",
                "create",
                "Circular Task",
                "--parent",
                "TSK-001",
                "--dependency",
                "TSK-001",
            ],
        )
        # Should detect and prevent circular dependencies


class TestOutputRedirection:
    """Test output redirection and file writing."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_output_to_file(self, mock_task_mgr, mock_exists, output_runner):
        """Test redirecting output to file."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-001", title="File Output Test", status="open", priority="medium"
            )
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            output_file = f.name

        try:
            result = output_runner.invoke(
                app,
                ["search", "file", "--output-file", output_file, "--format", "json"],
            )
            assert result.exit_code == 0

            # Check if file was created (if feature exists)
            if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                with open(output_file, "r") as f:
                    content = f.read()
                    assert len(content) > 0
        finally:
            if os.path.exists(output_file):
                os.unlink(output_file)

    def test_stdout_vs_stderr_separation(self, output_runner):
        """Test proper separation of stdout and stderr."""
        # Test command that should output to stderr (like errors)
        result = output_runner.invoke(app, ["--project-dir", "/nonexistent", "info"])
        assert result.exit_code == 1

        # Test command that should output to stdout (like normal output)
        result = output_runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert len(result.output) > 0


class TestUnicodeHandling:
    """Test handling of Unicode and special characters."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_unicode_task_titles(self, mock_create, mock_exists, output_runner):
        """Test handling of Unicode characters in task titles."""
        mock_exists.return_value = True
        mock_task = Mock(id="TSK-001", title="Unicode Test 🚀")
        mock_create.return_value = mock_task

        unicode_title = "Fix emoji support 🐛 and add internationalization 🌍"
        result = output_runner.invoke(app, ["task", "create", unicode_title])
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_unicode_search_queries(self, mock_task_mgr, mock_exists, output_runner):
        """Test Unicode characters in search queries."""
        mock_exists.return_value = True

        mock_tasks = [
            Mock(
                id="TSK-001",
                title="测试任务",
                description="Chinese test task",
                tags=["测试"],
                status="open",
            ),
            Mock(
                id="TSK-002",
                title="Тестовая задача",
                description="Russian test task",
                tags=["тест"],
                status="open",
            ),
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        # Test Chinese search
        result = output_runner.invoke(app, ["search", "测试"])
        assert result.exit_code == 0

        # Test Russian search
        result = output_runner.invoke(app, ["search", "Тестовая"])
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_special_characters_in_descriptions(
        self, mock_create, mock_exists, output_runner
    ):
        """Test special characters in task descriptions."""
        mock_exists.return_value = True
        mock_task = Mock(id="TSK-001", title="Special Chars Test")
        mock_create.return_value = mock_task

        special_description = "Task with special chars: @#$%^&*()_+-=[]{}|;':\",./<>?`~"
        result = output_runner.invoke(
            app,
            [
                "task",
                "create",
                "Special Chars Test",
                "--description",
                special_description,
            ],
        )
        assert result.exit_code == 0


class TestLargeDataHandling:
    """Test handling of large datasets."""

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager")
    def test_large_search_results(self, mock_task_mgr, mock_exists, output_runner):
        """Test handling of large search result sets."""
        mock_exists.return_value = True

        # Mock very large dataset
        mock_tasks = [
            Mock(
                id=f"TSK-{i:05d}",
                title=f"Large Dataset Task {i}",
                description=f"Task {i} in large dataset test",
                tags=[f"tag{i%10}"],
                status=["open", "in_progress", "completed"][i % 3],
                priority=["low", "medium", "high", "critical"][i % 4],
            )
            for i in range(1, 1001)  # 1000 tasks
        ]
        mock_task_mgr.return_value.list_tasks.return_value = mock_tasks

        result = output_runner.invoke(app, ["search", "large", "--limit", "100"])
        assert result.exit_code == 0
        # Should handle large datasets without performance issues

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_very_long_task_titles(self, mock_create, mock_exists, output_runner):
        """Test handling of very long task titles."""
        mock_exists.return_value = True
        mock_task = Mock(id="TSK-001", title="Very Long Title")
        mock_create.return_value = mock_task

        # Very long title (1000 characters)
        long_title = "Very " * 200  # 1000 characters
        result = output_runner.invoke(app, ["task", "create", long_title])
        assert result.exit_code == 0

    @patch("ai_trackdown_pytools.core.project.Project.exists")
    @patch("ai_trackdown_pytools.core.task.TaskManager.create_task")
    def test_many_tags_and_assignees(self, mock_create, mock_exists, output_runner):
        """Test handling of many tags and assignees."""
        mock_exists.return_value = True
        mock_task = Mock(id="TSK-001", title="Many Tags Test")
        mock_create.return_value = mock_task

        # Many tags
        many_tags = [f"tag{i}" for i in range(50)]
        many_assignees = [f"user{i}" for i in range(20)]

        result = output_runner.invoke(
            app,
            ["task", "create", "Many Tags Test", "--tag"]
            + many_tags[:10]
            + ["--assignee"]  # Limit to avoid command line length issues
            + many_assignees[:5],
        )
        assert result.exit_code == 0


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
