import pytest
from pathlib import Path
from click.testing import CliRunner
from unittest.mock import patch, MagicMock, ANY

try:
    # Python 3.9+ has native support for these types
    from typing import Generator
except ImportError:
    # For Python 3.8 support
    from typing_extensions import Generator

from plexomatic.cli import cli


@pytest.fixture
def runner() -> CliRunner:
    """Fixture that creates a CLI runner for testing commands."""
    return CliRunner()


@pytest.fixture(autouse=True)
def mock_config_db_path() -> Generator[None, None, None]:
    """Fixture that mocks the config.get_db_path method to return a test path."""
    with patch("plexomatic.cli.config.get_db_path", return_value=Path("/tmp/test-plexomatic.db")):
        yield


@pytest.fixture
def media_dir(tmp_path: Path) -> Path:
    """Fixture that creates a temporary directory with media files."""
    media_dir = tmp_path / "media"
    media_dir.mkdir()

    # Create test media files with some subdirectories to test progress
    media_dir.joinpath("subdir1").mkdir()
    media_dir.joinpath("subdir2").mkdir()

    test_files = [
        "The.Show.S01E01.mp4",
        "The.Show.S01E02.mp4",
        "Another Show S02E05 Episode.mkv",
        "Movie.2020.1080p.mp4",
        "subdir1/The.Show.S01E03.mp4",
        "subdir1/The.Show.S01E04.mp4",
        "subdir2/Another.Series.S03E01.mkv",
        "subdir2/Another.Series.S03E02.mkv",
    ]

    for filename in test_files:
        file_path = media_dir / filename
        file_path.parent.mkdir(exist_ok=True)
        file_path.write_text("dummy content")

    return media_dir


@patch("plexomatic.cli.cli_ui.progress_bar")
def test_scan_command_with_progress(
    mock_progress_bar: MagicMock, runner: CliRunner, media_dir: Path
) -> None:
    """Test that the scan command displays a progress bar."""
    # Set up mock progress
    mock_progress_instance = MagicMock()
    mock_task_id = 1
    mock_progress_tuple = (mock_progress_instance, mock_task_id)
    mock_progress_bar.return_value.__enter__.return_value = mock_progress_tuple

    result = runner.invoke(cli, ["scan", "--path", str(media_dir)])

    assert result.exit_code == 0
    assert "Scanning directory" in result.output

    # Check that progress bar is properly used
    mock_progress_bar.assert_called_once_with("Scanning for media files...")
    # Assert progress was updated during scanning (at least once)
    assert mock_progress_instance.update.call_count > 0
    # Assert progress was completed at the end
    mock_progress_instance.update.assert_any_call(mock_task_id, completed=True)


@patch("plexomatic.cli.rename_file")
@patch("plexomatic.cli.cli_ui.progress_bar")
def test_apply_command_with_progress(
    mock_progress_bar: MagicMock, mock_rename: MagicMock, runner: CliRunner, media_dir: Path
) -> None:
    """Test that the apply command shows progress during rename operations."""
    # Set up mock progress
    mock_progress_instance = MagicMock()
    mock_task_id = 1
    mock_progress_tuple = (mock_progress_instance, mock_task_id)
    mock_progress_bar.return_value.__enter__.return_value = mock_progress_tuple

    # Mock rename_file to return success
    mock_rename.return_value = True

    result = runner.invoke(cli, ["apply", "--path", str(media_dir)], input="y\n")

    assert result.exit_code == 0
    assert "Applying changes" in result.output

    # Check that progress bar is properly used - called twice in the flow (scan and apply)
    assert mock_progress_bar.call_count >= 1
    mock_progress_bar.assert_any_call("Renaming files...", total=ANY)

    # Assert progress was updated during renaming (at least once)
    assert mock_progress_instance.update.call_count > 0


@patch("plexomatic.cli.cli_ui.print_file_change")
def test_colored_output_in_preview(
    mock_file_change: MagicMock, runner: CliRunner, media_dir: Path
) -> None:
    """Test that the preview command uses colored output."""
    result = runner.invoke(cli, ["preview", "--path", str(media_dir)])

    assert result.exit_code == 0
    assert "Previewing changes" in result.output

    # Check that file change printer was called
    assert mock_file_change.call_count > 0


@patch("plexomatic.cli.cli_ui.print_status")
def test_status_indicators_in_commands(
    mock_status: MagicMock, runner: CliRunner, media_dir: Path
) -> None:
    """Test that commands show status indicators (success/error icons)."""
    # Run the scan command which should show success status
    result = runner.invoke(cli, ["scan", "--path", str(media_dir)])

    assert result.exit_code == 0

    # Check that print_status was called with a success status
    success_call_found = False
    for call_args in mock_status.call_args_list:
        args, kwargs = call_args
        if len(args) >= 1 and "success" in kwargs.get("status", ""):
            success_call_found = True
            break

    assert success_call_found, "No success status indicator found in output"


@patch("plexomatic.cli.cli_ui.print_heading")
def test_cli_help_has_colored_output(mock_heading: MagicMock, runner: CliRunner) -> None:
    """Test that CLI help text uses color."""
    # We need to run a command that uses colored headings
    result = runner.invoke(cli, ["configure"], input="\n\n\nn\nn\n")

    assert result.exit_code == 0
    # Configure command should use styled output for headings
    mock_heading.assert_called_with("Configuration", "Set up API keys and application settings")
