# tests/core/test_executor_hardening.py
import logging
from unittest.mock import MagicMock, patch

import pytest

import pdftl.core.constants as c
from pdftl.core.executor import _resolve_arguments, run_operation
from pdftl.core.registry import registry


def test_executor_floor_defaults():
    """Verify the 'Floor'. Empty context returns defaults, not KeyError."""
    arg_style = ([c.INPUT_PDF, c.INPUT_FILENAME], {c.ALIASES: c.ALIASES}, {})
    context = {}
    pos, kw = _resolve_arguments(arg_style, context)
    assert pos == [None, None]
    assert kw == {c.ALIASES: {}}


def test_executor_strict_indexing_typo():
    """Verify 'Strict Indexing' against registry-constant mismatches."""
    TYPO_CONSTANT = "this_key_does_not_exist_in_floor"
    arg_style = ([TYPO_CONSTANT], {}, {})
    with pytest.raises(KeyError):
        _resolve_arguments(arg_style, {})


def test_executor_successful_mapping():
    """Verify data delivery overrides floor."""
    arg_style = ([c.INPUT_PDF], {"output": c.OUTPUT}, {})
    mock_pdf = MagicMock()
    context = {c.INPUT_PDF: mock_pdf, c.OUTPUT: "final.pdf"}
    pos, kw = _resolve_arguments(arg_style, context)
    assert pos == [mock_pdf]
    assert kw == {"output": "final.pdf"}


def test_executor_full_run():
    """Integration test of run_operation using a mock registry."""
    mock_func = MagicMock(return_value="Success")

    class MockOp:
        def __init__(self):
            self.function = mock_func
            self.args = ([c.INPUT_FILENAME], {}, {})

    with patch("pdftl.core.executor.registry") as mock_registry:
        # We must mock .operations as a dict to satisfy the 'in' check in the executor
        mock_registry.operations = {"test_op": MockOp()}

        result = run_operation("test_op", {c.INPUT_FILENAME: "test.pdf"})

        assert result == "Success"
        mock_func.assert_called_once_with("test.pdf")


# tests/core/test_executor_coverage.py


def test_run_operation_unregistered():
    """
    Covers line 23: raise ValueError(f"Operation '{operation_name}' is not registered.")
    """
    with pytest.raises(ValueError, match="is not registered"):
        run_operation("non_existent_op", {})


def test_run_operation_malformed_entry(monkeypatch):
    """
    Covers line 29: raise ValueError(... is missing function or arg_style)
    """
    # Inject a bad registry entry
    monkeypatch.setitem(registry.operations, "bad_op", {"desc": "Missing stuff"})

    with pytest.raises(ValueError, match="missing function or arg_style"):
        run_operation("bad_op", {})


def test_run_operation_internal_error_logging(monkeypatch, caplog):
    """
    Covers line 40: logger.error("Internal error in operation '%s': %s", ...)
    """

    # 1. Define a dummy function that raises a generic Exception (not UserCommandLineError)
    def crashing_func(*args, **kwargs):
        raise RuntimeError("Something exploded")

    # 2. Register it temporarily
    op_data = {"function": crashing_func, "args": ([], {}, {})}  # Empty arg spec
    monkeypatch.setitem(registry.operations, "crashing_op", op_data)

    # 3. Run and verify it logs the error before re-raising
    with caplog.at_level(logging.ERROR):
        with pytest.raises(RuntimeError, match="Something exploded"):
            run_operation("crashing_op", {})

    assert "Internal error in operation 'crashing_op'" in caplog.text


# --- merged from test_executor_hardening.py ---


def test_executor_floor_defaults2():
    """
    Test 1: Verify the 'Floor'.
    If the context is empty, the executor should provide 'None' or '[]'
    rather than crashing, because the constants are defined in the floor.
    """
    # A registry-style arg definition asking for common constants
    arg_style = ([c.INPUT_PDF, c.INPUT_FILENAME], {c.ALIASES: c.ALIASES}, {})
    context = {}  # Empty context

    pos, kw = _resolve_arguments(arg_style, context)

    assert pos == [None, None]  # Default floor values
    assert kw == {c.ALIASES: {}}  # Default floor empty dict


def test_executor_strict_indexing_typo2():
    """
    Test 2: Verify 'Strict Indexing'.
    If the Registry asks for a key that is NOT in constants (a typo),
    it should raise a KeyError immediately.
    """
    TYPO_CONSTANT = "this_key_does_not_exist_in_floor"
    arg_style = ([TYPO_CONSTANT], {}, {})
    context = {}

    with pytest.raises(KeyError):
        # This proves the executor is 'hardened' against registry typos
        _resolve_arguments(arg_style, context)


def test_executor_successful_mapping2():
    """
    Test 3: Verify data delivery.
    Ensure that provided context values override the floor and reach the function.
    """
    arg_style = ([c.INPUT_PDF], {"output": c.OUTPUT}, {})
    mock_pdf = MagicMock()
    context = {c.INPUT_PDF: mock_pdf, c.OUTPUT: "final.pdf"}

    pos, kw = _resolve_arguments(arg_style, context)

    assert pos == [mock_pdf]
    assert kw == {"output": "final.pdf"}


def test_executor_full_run2():
    """
    Test 4: Integration test of run_operation.
    Mocks the registry operations dictionary to ensure the flow from
    op_name to function call is solid.
    """
    mock_func = MagicMock(return_value="Success")

    # Mocking the operation data object
    class MockOp:
        def __init__(self):
            self.function = mock_func
            self.args = ([c.INPUT_FILENAME], {}, {})

    # We patch the operations dictionary directly.
    # This satisfies the 'key in src' check in _get_keyval_or_attr if it uses dict lookup,
    # or getattr if it uses object lookup.
    # Note: run_operation uses _get_keyval_or_attr(registry.operations, op_name)

    with patch("pdftl.core.executor.registry") as mock_registry:
        # Configure the mock registry to behave like a dict for the lookup
        mock_registry.operations = {"test_op": MockOp()}

        result = run_operation("test_op", {c.INPUT_FILENAME: "test.pdf"})

        assert result == "Success"
        mock_func.assert_called_once_with("test.pdf")
