# tests/test_infra_cli.py
import pytest
import json
from unittest.mock import patch, MagicMock, Mock
from pathlib import Path

from aicodec.infrastructure.cli.command_line_interface import check_config_exists
from aicodec.infrastructure.cli.commands import (
    aggregate,
    apply,
    init,
    prepare,
    prompt,
    revert,
    schema,
)


@pytest.fixture
def temp_config_file(tmp_path):
    config_dir = tmp_path / '.aicodec'
    config_dir.mkdir()
    config_file = config_dir / 'config.json'
    config_data = {
        "aggregate": {"directory": "."},
        "prompt": {"output_file": str(config_dir / "prompt.txt")},
        "prepare": {"changes": str(config_dir / 'changes.json'), "from_clipboard": False},
        "apply": {"output_dir": "."}
    }
    config_file.write_text(json.dumps(config_data))
    return config_file


@patch('aicodec.infrastructure.cli.commands.init.load_default_prompt_template')
def test_init_run_interactive(mock_load_template, tmp_path, monkeypatch):
    mock_load_template.return_value = "dummy template"

    user_inputs = [
        'y', 'y', 'y', 'src,lib', '*.ts', '.ts,.js',
        'node_modules', '*.log', '.log', 'n'
    ]
    with patch('builtins.input', side_effect=user_inputs):
        monkeypatch.chdir(tmp_path)
        init.run(MagicMock())

    config_file = tmp_path / '.aicodec' / 'config.json'
    assert config_file.exists()
    config = json.loads(config_file.read_text())

    assert config['aggregate']['use_gitignore'] is True
    assert '.gitignore' in config['aggregate']['exclude_files']
    assert 'src' in config['aggregate']['include_dirs']
    assert 'node_modules' in config['aggregate']['exclude_dirs']
    assert config['prompt']['template'] == "dummy template"


def test_check_config_exists_fail(capsys):
    with pytest.raises(SystemExit) as e:
        check_config_exists('non_existent_file.json')
    assert e.value.code == 1
    captured = capsys.readouterr()
    assert "aicodec not initialised" in captured.out


def test_schema_run(capsys):
    with patch('aicodec.infrastructure.cli.commands.schema.files') as mock_files:
        mock_schema_path = MagicMock()
        mock_schema_path.read_text.return_value = '{"schema": true}'
        mock_assets = MagicMock()
        mock_assets.__truediv__.return_value = mock_schema_path
        mock_aicodec = MagicMock()
        mock_aicodec.__truediv__.return_value = mock_assets
        mock_files.return_value = mock_aicodec
        schema.run(None)
    captured = capsys.readouterr()
    assert '{"schema": true}' in captured.out


def test_schema_run_not_found(capsys):
    with patch('aicodec.infrastructure.cli.commands.schema.files') as mock_files:
        mock_schema_path = MagicMock()
        mock_schema_path.read_text.side_effect = FileNotFoundError
        mock_assets = MagicMock()
        mock_assets.__truediv__.return_value = mock_schema_path
        mock_aicodec = MagicMock()
        mock_aicodec.__truediv__.return_value = mock_assets
        mock_files.return_value = mock_aicodec
        with pytest.raises(SystemExit) as e:
            schema.run(None)
    assert e.value.code == 1
    captured = capsys.readouterr()
    assert "schema.json not found" in captured.err


def test_init_overwrite_cancel(tmp_path):
    config_file = tmp_path / '.aicodec' / 'config.json'
    config_file.parent.mkdir()
    config_file.touch()
    with patch('builtins.input', return_value='n'):
        with patch('aicodec.infrastructure.cli.commands.init.load_default_prompt_template'):
            init.run(None)
    assert config_file.read_text() == ""


def test_aggregate_run(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.aggregate.AggregationService') as mock_agg_service_class:
        mock_agg_service_instance = mock_agg_service_class.return_value

        args = MagicMock(
            config=str(temp_config_file), directory=None, include_dir=[], include_ext=[],
            include_file=[], exclude_dir=[], exclude_ext=[], exclude_file=[],
            full=True, use_gitignore=None, count_tokens=True
        )
        aggregate.run(args)
        mock_agg_service_class.assert_called_once()
        mock_agg_service_instance.aggregate.assert_called_once_with(
            full_run=True, count_tokens=True)


def test_prompt_run(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.prompt.open_file_in_editor') as mock_open:
        with patch('aicodec.infrastructure.cli.commands.prompt.files') as mock_files:
            with patch('aicodec.infrastructure.cli.commands.prompt.load_default_prompt_template') as mock_template:
                mock_schema_path = MagicMock()
                mock_schema_path.read_text.return_value = '{"schema": true}'
                mock_assets = MagicMock()
                mock_assets.__truediv__.return_value = mock_schema_path
                mock_aicodec = MagicMock()
                mock_aicodec.__truediv__.return_value = mock_assets
                mock_files.return_value = mock_aicodec
                mock_template.return_value = 'template with {user_task_description}'

                context_file = temp_config_file.parent / 'context.json'
                context_file.write_text('[]')

                args = MagicMock(config=str(
                    temp_config_file), task='test task', output_file=None, clipboard=False)

                with patch('pathlib.Path.write_text') as mock_write:
                    prompt.run(args)

                    mock_write.assert_called_once()
                    call_args = mock_write.call_args
                    assert 'template with test task' in call_args[0][0]


def test_prompt_run_to_clipboard(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.prompt.pyperclip') as mock_pyperclip:
        with patch('pathlib.Path.is_file', return_value=True):
            with patch('pathlib.Path.read_text', return_value='[]'):
                with patch('aicodec.infrastructure.cli.commands.prompt.files'):
                    with patch('aicodec.infrastructure.cli.commands.prompt.load_default_prompt_template', return_value='template'):
                        args = MagicMock(config=str(
                            temp_config_file), task='test task', output_file=None, clipboard=True)
                        prompt.run(args)

                        mock_pyperclip.copy.assert_called_once_with('template')


def test_apply_run(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.apply.ReviewService') as mock_review_service:
        with patch('aicodec.infrastructure.cli.commands.apply.launch_review_server') as mock_launch_server:
            args = MagicMock(config=str(temp_config_file),
                             output_dir=None, changes=None)
            apply.run(args)
            mock_review_service.assert_called_once()
            mock_launch_server.assert_called_once_with(
                mock_review_service.return_value, mode='apply')


def test_revert_run(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.revert.ReviewService') as mock_review_service:
        with patch('aicodec.infrastructure.cli.commands.revert.launch_review_server') as mock_launch_server:
            with patch('pathlib.Path.is_file', return_value=True):
                args = MagicMock(config=str(temp_config_file),
                                 output_dir=str(temp_config_file.parent.parent))
                revert.run(args)

                mock_review_service.assert_called_once()
                mock_launch_server.assert_called_once_with(
                    mock_review_service.return_value, mode='revert')


def test_prepare_run_from_clipboard_success(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.prepare.pyperclip') as mock_pyperclip:
        with patch('aicodec.infrastructure.cli.commands.prepare.files') as mock_files:
            with patch('jsonschema.validate'):
                mock_schema_path = MagicMock()
                mock_schema_path.read_text.return_value = '{"schema": true}'
                mock_assets = MagicMock()
                mock_assets.__truediv__.return_value = mock_schema_path
                mock_aicodec = MagicMock()
                mock_aicodec.__truediv__.return_value = mock_assets
                mock_files.return_value = mock_aicodec
                valid_json = '{"changes": [{"filePath": "a.py", "action": "CREATE", "content": ""}]}'
                mock_pyperclip.paste.return_value = valid_json

                args = MagicMock(config=str(temp_config_file),
                                 changes=None, from_clipboard=True)
                prepare.run(args)

                changes_path = temp_config_file.parent / 'changes.json'
                assert changes_path.read_text() == valid_json


def test_prepare_run_open_editor(temp_config_file):
    with patch('aicodec.infrastructure.cli.commands.prepare.open_file_in_editor') as mock_open_editor:
        args = MagicMock(config=str(temp_config_file),
                         changes=None, from_clipboard=False)
        prepare.run(args)
        mock_open_editor.assert_called_once()
