"""Editor-related tests for the DockerRuntime."""

import os

from conftest import _close_test_runtime, _load_runtime

from openhands.core.logger import openhands_logger as logger
from openhands.events.action import FileEditAction, FileWriteAction


def test_view_file(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create test file
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.\nThis file is for testing purposes.',
            path=test_file,
        )
        obs = runtime.run_action(action)

        # Test view command
        action = FileEditAction(
            command='view',
            path=test_file,
        )
        obs = runtime.run_action(action)

        assert f"Here's the result of running `cat -n` on {test_file}:" in obs.content
        assert '1\tThis is a test file.' in obs.content
        assert '2\tThis file is for testing purposes.' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_view_directory(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create test file
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.\nThis file is for testing purposes.',
            path=test_file,
        )
        obs = runtime.run_action(action)

        # Test view command
        action = FileEditAction(
            command='view',
            path=config.workspace_mount_path_in_sandbox,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            obs.content
            == f"""Here's the files and directories up to 2 levels deep in {config.workspace_mount_path_in_sandbox}, excluding hidden items:
{config.workspace_mount_path_in_sandbox}/
{config.workspace_mount_path_in_sandbox}/test.txt"""
        )

    finally:
        _close_test_runtime(runtime)


def test_create_file(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        new_file = os.path.join(config.workspace_mount_path_in_sandbox, 'new_file.txt')
        action = FileEditAction(
            command='create',
            path=new_file,
            file_text='New file content',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'File created successfully' in obs.content

        # Verify file content
        action = FileEditAction(
            command='view',
            path=new_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'New file content' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_create_file_with_empty_content(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        new_file = os.path.join(config.workspace_mount_path_in_sandbox, 'new_file.txt')
        action = FileEditAction(
            command='create',
            path=new_file,
            file_text='',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'File created successfully' in obs.content

        # Verify file content
        action = FileEditAction(
            command='view',
            path=new_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert '1\t' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_create_with_none_file_text(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        new_file = os.path.join(
            config.workspace_mount_path_in_sandbox, 'none_content.txt'
        )
        action = FileEditAction(
            command='create',
            path=new_file,
            file_text=None,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            obs.content
            == 'ERROR:\nParameter `file_text` is required for command: create.'
        )
    finally:
        _close_test_runtime(runtime)


def test_str_replace(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create test file
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.\nThis file is for testing purposes.',
            path=test_file,
        )
        runtime.run_action(action)

        # Test str_replace command
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='test file',
            new_str='sample file',
        )
        obs = runtime.run_action(action)
        assert f'The file {test_file} has been edited' in obs.content

        # Verify file content
        action = FileEditAction(
            command='view',
            path=test_file,
        )
        obs = runtime.run_action(action)
        assert 'This is a sample file.' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_str_replace_multi_line(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.\nThis file is for testing purposes.',
            path=test_file,
        )
        runtime.run_action(action)

        # Test str_replace command
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='This is a test file.\nThis file is for testing purposes.',
            new_str='This is a sample file.\nThis file is for testing purposes.',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert f'The file {test_file} has been edited.' in obs.content
        assert 'This is a sample file.' in obs.content
        assert 'This file is for testing purposes.' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_str_replace_multi_line_with_tabs(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='def test():\n\tprint("Hello, World!")',
            path=test_file,
        )
        runtime.run_action(action)

        # Test str_replace command
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='def test():\n\tprint("Hello, World!")',
            new_str='def test():\n\tprint("Hello, Universe!")',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            obs.content
            == f"""The file {test_file} has been edited. Here's the result of running `cat -n` on a snippet of {test_file}:
     1\tdef test():
     2\t{'\t'.expandtabs()}print("Hello, Universe!")
     3\t
Review the changes and make sure they are as expected. Edit the file again if necessary."""
        )

    finally:
        _close_test_runtime(runtime)


def test_str_replace_error_multiple_occurrences(
    temp_dir, runtime_cls, run_as_openhands
):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.\nThis file is for testing purposes.',
            path=test_file,
        )
        runtime.run_action(action)

        action = FileEditAction(
            command='str_replace', path=test_file, old_str='test', new_str='sample'
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Multiple occurrences of old_str `test`' in obs.content
        assert '[1, 2]' in obs.content  # Should show both line numbers
    finally:
        _close_test_runtime(runtime)


def test_str_replace_error_multiple_multiline_occurrences(
    temp_dir, runtime_cls, run_as_openhands
):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        # Create a file with two identical multi-line blocks
        multi_block = """def example():
        print("Hello")
        return True"""
        content = f"{multi_block}\n\nprint('separator')\n\n{multi_block}"
        action = FileWriteAction(
            content=content,
            path=test_file,
        )
        runtime.run_action(action)

        # Test str_replace command
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str=multi_block,
            new_str='def new():\n    print("World")',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Multiple occurrences of old_str' in obs.content
        assert '[1, 7]' in obs.content  # Should show correct starting line numbers

    finally:
        _close_test_runtime(runtime)


def test_str_replace_nonexistent_string(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='Non-existent Line',
            new_str='New Line',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'No replacement was performed' in obs.content
        assert (
            f'old_str `Non-existent Line` did not appear verbatim in {test_file}'
            in obs.content
        )
    finally:
        _close_test_runtime(runtime)


def test_str_replace_with_empty_new_str(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine to remove\nLine 3',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='Line to remove\n',
            new_str='',
        )
        obs = runtime.run_action(action)
        assert 'Line to remove' not in obs.content
        assert 'Line 1' in obs.content
        assert 'Line 3' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_str_replace_with_empty_old_str(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2\nLine 3',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='',
            new_str='New string',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            'No replacement was performed. Multiple occurrences of old_str `` in lines [1, 2, 3, 4]. Please ensure it is unique.'
            in obs.content
        )
    finally:
        _close_test_runtime(runtime)


def test_str_replace_with_none_old_str(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2\nLine 3',
            path=test_file,
        )
        runtime.run_action(action)

        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str=None,
            new_str='new content',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'old_str' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_insert(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create test file
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)

        # Test insert command
        action = FileEditAction(
            command='insert',
            path=test_file,
            insert_line=1,
            new_str='Inserted line',
        )
        obs = runtime.run_action(action)
        assert f'The file {test_file} has been edited' in obs.content

        # Verify file content
        action = FileEditAction(
            command='view',
            path=test_file,
        )
        obs = runtime.run_action(action)
        assert 'Line 1' in obs.content
        assert 'Inserted line' in obs.content
        assert 'Line 2' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_insert_invalid_line(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='insert',
            path=test_file,
            insert_line=10,
            new_str='Invalid Insert',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Invalid `insert_line` parameter' in obs.content
        assert 'It should be within the range of lines of the file' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_insert_with_empty_string(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='insert',
            path=test_file,
            insert_line=1,
            new_str='',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert '1\tLine 1' in obs.content
        assert '2\t\n' in obs.content
        assert '3\tLine 2' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_insert_with_none_new_str(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)

        action = FileEditAction(
            command='insert',
            path=test_file,
            insert_line=1,
            new_str=None,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'ERROR' in obs.content
        assert 'Parameter `new_str` is required for command: insert' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_undo_edit(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create test file
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='This is a test file.',
            path=test_file,
        )
        runtime.run_action(action)

        # Make an edit
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='test',
            new_str='sample',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'This is a sample file.' in obs.content

        # Undo the edit
        action = FileEditAction(
            command='undo_edit',
            path=test_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Last edit to' in obs.content
        assert 'This is a test file.' in obs.content

        # Verify file content
        action = FileEditAction(
            command='view',
            path=test_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'This is a test file.' in obs.content

    finally:
        _close_test_runtime(runtime)


def test_validate_path_invalid(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        invalid_file = os.path.join(
            config.workspace_mount_path_in_sandbox, 'nonexistent.txt'
        )
        action = FileEditAction(
            command='view',
            path=invalid_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Invalid `path` parameter' in obs.content
        assert f'The path {invalid_file} does not exist' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_create_existing_file_error(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='create',
            path=test_file,
            file_text='New content',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'File already exists' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_str_replace_missing_old_str(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='',
            new_str='sample',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            'No replacement was performed. Multiple occurrences of old_str ``'
            in obs.content
        )
    finally:
        _close_test_runtime(runtime)


def test_str_replace_new_str_and_old_str_same(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='str_replace',
            path=test_file,
            old_str='test file',
            new_str='test file',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            'No replacement was performed. `new_str` and `old_str` must be different.'
            in obs.content
        )
    finally:
        _close_test_runtime(runtime)


def test_insert_missing_line_param(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt')
        action = FileWriteAction(
            content='Line 1\nLine 2',
            path=test_file,
        )
        runtime.run_action(action)
        action = FileEditAction(
            command='insert',
            path=test_file,
            new_str='Missing insert line',
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'Parameter `insert_line` is required for command: insert' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_undo_edit_no_history_error(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        empty_file = os.path.join(config.workspace_mount_path_in_sandbox, 'empty.txt')
        action = FileWriteAction(
            content='',
            path=empty_file,
        )
        runtime.run_action(action)

        action = FileEditAction(
            command='undo_edit',
            path=empty_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert 'No edit history found for' in obs.content
    finally:
        _close_test_runtime(runtime)


def test_view_large_file_with_truncation(temp_dir, runtime_cls, run_as_openhands):
    runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands)
    try:
        # Create a large file to trigger truncation
        large_file = os.path.join(
            config.workspace_mount_path_in_sandbox, 'large_test.txt'
        )
        large_content = 'Line 1\n' * 16000  # 16000 lines should trigger truncation
        action = FileWriteAction(
            content=large_content,
            path=large_file,
        )
        runtime.run_action(action)

        action = FileEditAction(
            command='view',
            path=large_file,
        )
        obs = runtime.run_action(action)
        logger.info(obs, extra={'msg_type': 'OBSERVATION'})
        assert (
            'Due to the max output limit, only part of this file has been shown to you.'
            in obs.content
        )
    finally:
        _close_test_runtime(runtime)
