from typing import TypedDict

from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.schema import AgentState
from openhands.events.action import (
    Action,
    AgentFinishAction,
    AgentRejectAction,
    CmdRunAction,
    FileReadAction,
    FileWriteAction,
    MessageAction,
)
from openhands.events.observation import (
    AgentStateChangedObservation,
    CmdOutputMetadata,
    CmdOutputObservation,
    FileReadObservation,
    FileWriteObservation,
    Observation,
)
from openhands.events.serialization.event import event_to_dict
from openhands.llm.llm_registry import LLMRegistry

"""
FIXME: There are a few problems this surfaced
* FileWrites seem to add an unintended newline at the end of the file
* Browser not working
"""

ActionObs = TypedDict(
    'ActionObs', {'action': Action, 'observations': list[Observation]}
)


class DummyAgent(Agent):
    VERSION = '1.0'
    """
    The DummyAgent is used for e2e testing. It just sends the same set of actions deterministically,
    without making any LLM calls.
    """

    def __init__(
        self,
        config: AgentConfig,
        llm_registry: LLMRegistry,
    ):
        super().__init__(config, llm_registry)
        self.steps: list[ActionObs] = [
            {
                'action': MessageAction('Time to get started!'),
                'observations': [],
            },
            {
                'action': CmdRunAction(command='echo "foo"'),
                'observations': [
                    CmdOutputObservation(
                        'foo',
                        command='echo "foo"',
                        metadata=CmdOutputMetadata(exit_code=0),
                    )
                ],
            },
            {
                'action': FileWriteAction(
                    content='echo "Hello, World!"', path='hello.sh'
                ),
                'observations': [FileWriteObservation(content='', path='hello.sh')],
            },
            {
                'action': FileReadAction(path='hello.sh'),
                'observations': [
                    FileReadObservation('echo "Hello, World!"\n', path='hello.sh')
                ],
            },
            {
                'action': CmdRunAction(command='bash hello.sh'),
                'observations': [
                    CmdOutputObservation(
                        'Hello, World!',
                        command='bash hello.sh',
                        metadata=CmdOutputMetadata(exit_code=0),
                    )
                ],
            },
            {
                'action': AgentRejectAction(),
                'observations': [AgentStateChangedObservation('', AgentState.REJECTED)],
            },
            {
                'action': AgentFinishAction(
                    outputs={}, thought='Task completed', action='finish'
                ),
                'observations': [AgentStateChangedObservation('', AgentState.FINISHED)],
            },
        ]

    def step(self, state: State) -> Action:
        if state.iteration_flag.current_value >= len(self.steps):
            return AgentFinishAction()

        current_step = self.steps[state.iteration_flag.current_value]
        action = current_step['action']

        if state.iteration_flag.current_value > 0:
            prev_step = self.steps[state.iteration_flag.current_value - 1]

            if 'observations' in prev_step and prev_step['observations']:
                expected_observations = prev_step['observations']
                hist_events = state.view[-len(expected_observations) :]

                if len(hist_events) < len(expected_observations):
                    print(
                        f'Warning: Expected {len(expected_observations)} observations, but got {len(hist_events)}'
                    )

                for i in range(min(len(expected_observations), len(hist_events))):
                    hist_obs = event_to_dict(hist_events[i])
                    expected_obs = event_to_dict(expected_observations[i])

                    # Remove dynamic fields for comparison
                    for obs in [hist_obs, expected_obs]:
                        obs.pop('id', None)
                        obs.pop('timestamp', None)
                        obs.pop('cause', None)
                        obs.pop('source', None)
                        # Remove dynamic metadata fields that vary between runs
                        if 'extras' in obs and 'metadata' in obs['extras']:
                            metadata = obs['extras']['metadata']
                            if isinstance(metadata, dict):
                                metadata.pop('pid', None)
                                metadata.pop('username', None)
                                metadata.pop('hostname', None)
                                metadata.pop('working_dir', None)
                                metadata.pop('py_interpreter_path', None)
                                metadata.pop('suffix', None)
                        # Normalize file paths for comparison - extract just the filename
                        if 'extras' in obs and 'path' in obs['extras']:
                            path = obs['extras']['path']
                            if isinstance(path, str):
                                # Extract just the filename from the path
                                import os

                                obs['extras']['path'] = os.path.basename(path)
                        # Normalize message field to handle path differences
                        if 'message' in obs:
                            import os

                            message = obs['message']
                            if isinstance(message, str):
                                # Replace full paths with just filenames in messages
                                if 'I wrote to the file ' in message:
                                    parts = message.split('I wrote to the file ')
                                    if len(parts) == 2:
                                        filename = os.path.basename(
                                            parts[1].rstrip('.')
                                        )
                                        obs['message'] = (
                                            f'I wrote to the file {filename}.'
                                        )
                                elif 'I read the file ' in message:
                                    parts = message.split('I read the file ')
                                    if len(parts) == 2:
                                        filename = os.path.basename(
                                            parts[1].rstrip('.')
                                        )
                                        obs['message'] = f'I read the file {filename}.'

                    if hist_obs != expected_obs:
                        print(
                            f'Warning: Observation mismatch. Expected {expected_obs}, got {hist_obs}'
                        )

        return action
