import json

from openhands.events.observation.commands import (
    CMD_OUTPUT_METADATA_PS1_REGEX,
    CMD_OUTPUT_PS1_BEGIN,
    CMD_OUTPUT_PS1_END,
    CmdOutputMetadata,
    CmdOutputObservation,
)


def test_ps1_metadata_format():
    """Test that PS1 prompt has correct format markers"""
    prompt = CmdOutputMetadata.to_ps1_prompt()
    print(prompt)
    assert prompt.startswith('\n###PS1JSON###\n')
    assert prompt.endswith('\n###PS1END###\n')
    assert r'\"exit_code\"' in prompt, 'PS1 prompt should contain escaped double quotes'


def test_ps1_metadata_json_structure():
    """Test that PS1 prompt contains valid JSON with expected fields"""
    prompt = CmdOutputMetadata.to_ps1_prompt()
    # Extract JSON content between markers
    json_str = prompt.replace('###PS1JSON###\n', '').replace('\n###PS1END###\n', '')
    # Remove escaping before parsing
    json_str = json_str.replace(r'\"', '"')
    # Remove any trailing content after the JSON
    json_str = json_str.split('###PS1END###')[0].strip()
    data = json.loads(json_str)

    # Check required fields
    expected_fields = {
        'pid',
        'exit_code',
        'username',
        'hostname',
        'working_dir',
        'py_interpreter_path',
    }
    assert set(data.keys()) == expected_fields


def test_ps1_metadata_parsing():
    """Test parsing PS1 output into CmdOutputMetadata"""
    test_data = {
        'exit_code': 0,
        'username': 'testuser',
        'hostname': 'localhost',
        'working_dir': '/home/testuser',
        'py_interpreter_path': '/usr/bin/python',
    }

    ps1_str = f"""###PS1JSON###
{json.dumps(test_data, indent=2)}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == test_data['exit_code']
    assert metadata.username == test_data['username']
    assert metadata.hostname == test_data['hostname']
    assert metadata.working_dir == test_data['working_dir']
    assert metadata.py_interpreter_path == test_data['py_interpreter_path']


def test_ps1_metadata_parsing_string():
    """Test parsing PS1 output into CmdOutputMetadata"""
    ps1_str = r"""###PS1JSON###
{
  "exit_code": "0",
  "username": "myname",
  "hostname": "myhostname",
  "working_dir": "~/mydir",
  "py_interpreter_path": "/my/python/path"
}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.username == 'myname'
    assert metadata.hostname == 'myhostname'
    assert metadata.working_dir == '~/mydir'
    assert metadata.py_interpreter_path == '/my/python/path'


def test_ps1_metadata_parsing_string_real_example():
    """Test parsing PS1 output into CmdOutputMetadata"""
    ps1_str = r"""
###PS1JSON###
{
  "pid": "",
  "exit_code": "0",
  "username": "runner",
  "hostname": "fv-az1055-610",
  "working_dir": "/home/runner/work/OpenHands/OpenHands",
  "py_interpreter_path": "/home/runner/.cache/pypoetry/virtualenvs/openhands-ai-ULPBlkAi-py3.12/bin/python"
}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.username == 'runner'
    assert metadata.hostname == 'fv-az1055-610'
    assert metadata.working_dir == '/home/runner/work/OpenHands/OpenHands'
    assert (
        metadata.py_interpreter_path
        == '/home/runner/.cache/pypoetry/virtualenvs/openhands-ai-ULPBlkAi-py3.12/bin/python'
    )


def test_ps1_metadata_parsing_additional_prefix():
    """Test parsing PS1 output into CmdOutputMetadata"""
    test_data = {
        'exit_code': 0,
        'username': 'testuser',
        'hostname': 'localhost',
        'working_dir': '/home/testuser',
        'py_interpreter_path': '/usr/bin/python',
    }

    ps1_str = f"""
This is something that not part of the PS1 prompt

###PS1JSON###
{json.dumps(test_data, indent=2)}
###PS1END###
"""

    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == test_data['exit_code']
    assert metadata.username == test_data['username']
    assert metadata.hostname == test_data['hostname']
    assert metadata.working_dir == test_data['working_dir']
    assert metadata.py_interpreter_path == test_data['py_interpreter_path']


def test_ps1_metadata_parsing_invalid():
    """Test parsing invalid PS1 output returns default metadata"""
    # Test with invalid JSON
    invalid_json = """###PS1JSON###
    {invalid json}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(invalid_json)
    assert len(matches) == 0  # No matches should be found for invalid JSON

    # Test with missing markers
    invalid_format = """NOT A VALID PS1 PROMPT"""
    matches = CmdOutputMetadata.matches_ps1_metadata(invalid_format)
    assert len(matches) == 0

    # Test with empty PS1 metadata
    empty_metadata = """###PS1JSON###

###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(empty_metadata)
    assert len(matches) == 0  # No matches should be found for empty metadata

    # Test with whitespace in PS1 metadata
    whitespace_metadata = """###PS1JSON###

    {
        "exit_code": "0",
        "pid": "123",
        "username": "test",
        "hostname": "localhost",
        "working_dir": "/home/test",
        "py_interpreter_path": "/usr/bin/python"
    }

###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(whitespace_metadata)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.pid == 123


def test_ps1_metadata_missing_fields():
    """Test handling of missing fields in PS1 metadata"""
    # Test with only required fields
    minimal_data = {'exit_code': 0, 'pid': 123}
    ps1_str = f"""###PS1JSON###
{json.dumps(minimal_data)}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.pid == 123
    assert metadata.username is None
    assert metadata.hostname is None
    assert metadata.working_dir is None
    assert metadata.py_interpreter_path is None

    # Test with missing exit_code but valid pid
    no_exit_code = {'pid': 123, 'username': 'test'}
    ps1_str = f"""###PS1JSON###
{json.dumps(no_exit_code)}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == -1  # default value
    assert metadata.pid == 123
    assert metadata.username == 'test'


def test_ps1_metadata_multiple_blocks():
    """Test handling multiple PS1 metadata blocks"""
    test_data = {
        'exit_code': 0,
        'username': 'testuser',
        'hostname': 'localhost',
        'working_dir': '/home/testuser',
        'py_interpreter_path': '/usr/bin/python',
    }

    ps1_str = f"""###PS1JSON###
{json.dumps(test_data, indent=2)}
###PS1END###
Some other content
###PS1JSON###
{json.dumps(test_data, indent=2)}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 2  # Should find both blocks
    # Both blocks should parse successfully
    metadata1 = CmdOutputMetadata.from_ps1_match(matches[0])
    metadata2 = CmdOutputMetadata.from_ps1_match(matches[1])
    assert metadata1.exit_code == test_data['exit_code']
    assert metadata2.exit_code == test_data['exit_code']


def test_ps1_metadata_regex_pattern():
    """Test the regex pattern used to extract PS1 metadata"""
    # Test basic pattern matching
    test_str = f'{CMD_OUTPUT_PS1_BEGIN}test\n{CMD_OUTPUT_PS1_END}'
    matches = CMD_OUTPUT_METADATA_PS1_REGEX.finditer(test_str)
    match = next(matches)
    assert match.group(1).strip() == 'test'

    # Test with content before and after
    test_str = f'prefix\n{CMD_OUTPUT_PS1_BEGIN}test\n{CMD_OUTPUT_PS1_END}suffix'
    matches = CMD_OUTPUT_METADATA_PS1_REGEX.finditer(test_str)
    match = next(matches)
    assert match.group(1).strip() == 'test'

    # Test with multiline content
    test_str = f'{CMD_OUTPUT_PS1_BEGIN}line1\nline2\nline3\n{CMD_OUTPUT_PS1_END}'
    matches = CMD_OUTPUT_METADATA_PS1_REGEX.finditer(test_str)
    match = next(matches)
    assert match.group(1).strip() == 'line1\nline2\nline3'


def test_cmd_output_observation_properties():
    """Test CmdOutputObservation class properties"""
    # Test with successful command
    metadata = CmdOutputMetadata(exit_code=0, pid=123)
    obs = CmdOutputObservation(command='ls', content='file1\nfile2', metadata=metadata)
    assert obs.command_id == 123
    assert obs.exit_code == 0
    assert not obs.error
    assert 'exit code 0' in obs.message
    assert 'ls' in obs.message
    assert 'file1' in str(obs)
    assert 'file2' in str(obs)
    assert 'metadata' in str(obs)

    # Test with failed command
    metadata = CmdOutputMetadata(exit_code=1, pid=456)
    obs = CmdOutputObservation(command='invalid', content='error', metadata=metadata)
    assert obs.command_id == 456
    assert obs.exit_code == 1
    assert obs.error
    assert 'exit code 1' in obs.message
    assert 'invalid' in obs.message
    assert 'error' in str(obs)


def test_ps1_metadata_empty_fields():
    """Test handling of empty fields in PS1 metadata"""
    # Test with empty strings
    empty_data = {
        'exit_code': 0,
        'pid': 123,
        'username': '',
        'hostname': '',
        'working_dir': '',
        'py_interpreter_path': '',
    }
    ps1_str = f"""###PS1JSON###
{json.dumps(empty_data)}
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(ps1_str)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.pid == 123
    assert metadata.username == ''
    assert metadata.hostname == ''
    assert metadata.working_dir == ''
    assert metadata.py_interpreter_path == ''

    # Test with malformed but valid JSON
    malformed_json = """###PS1JSON###
    {
        "exit_code":0,
        "pid"  :  123,
        "username":    "test"  ,
        "hostname": "host",
        "working_dir"    :"dir",
        "py_interpreter_path":"path"
    }
###PS1END###
"""
    matches = CmdOutputMetadata.matches_ps1_metadata(malformed_json)
    assert len(matches) == 1
    metadata = CmdOutputMetadata.from_ps1_match(matches[0])
    assert metadata.exit_code == 0
    assert metadata.pid == 123
    assert metadata.username == 'test'
    assert metadata.hostname == 'host'
    assert metadata.working_dir == 'dir'
    assert metadata.py_interpreter_path == 'path'
