"""Execute generated extraction scripts in a subprocess.

The agent generates `def extract(page) -> dict`. This module wraps it
with boilerplate (PDF open, page selection, JSON output) and runs it
in a subprocess with timeout.
"""

from __future__ import annotations

import json
import subprocess
import tempfile
import time
import traceback
from pathlib import Path

from agentic.config import AgentConfig
from agentic.models import ExecutionResult


def build_wrapper_script(
    user_script: str,
    pdf_path: str,
    page_number: int | None = None,
) -> str:
    """Wrap the agent's extract() function with PDF open/close boilerplate."""
    return f"""\
import json
import re
from datetime import datetime
from natural_pdf import PDF

# ── Agent-generated extraction code ──────────────────────────────────────

{user_script}

# ── Wrapper ──────────────────────────────────────────────────────────────

pdf = PDF({pdf_path!r})
result = extract(pdf)
print(json.dumps(result, default=str))
pdf.close()
"""


def execute_script(
    script: str,
    pdf_path: str,
    config: AgentConfig,
) -> ExecutionResult:
    """Run a generated extraction script in a subprocess.

    Returns ExecutionResult with output dict, timing, and any errors.
    """
    wrapped = build_wrapper_script(script, pdf_path)

    # Write to temp file
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".py", delete=False, prefix="npdf_agent_"
    ) as f:
        f.write(wrapped)
        temp_path = f.name

    start = time.monotonic()

    try:
        result = subprocess.run(
            [config.python_command, temp_path],
            capture_output=True,
            text=True,
            timeout=config.timeout_seconds,
        )
        duration_ms = int((time.monotonic() - start) * 1000)

        if result.returncode != 0:
            # Script crashed
            tb = result.stderr.strip()
            # Extract the last line as the error message
            error_lines = tb.split("\n")
            error_msg = error_lines[-1] if error_lines else "Unknown error"

            return ExecutionResult(
                success=False,
                stdout=result.stdout,
                stderr=result.stderr,
                error_type="runtime",
                error_message=error_msg,
                duration_ms=duration_ms,
                traceback=tb,
            )

        # Try to parse JSON from stdout
        stdout = result.stdout.strip()
        if not stdout:
            return ExecutionResult(
                success=False,
                stdout=result.stdout,
                stderr=result.stderr,
                error_type="json_parse",
                error_message="Script produced no output",
                duration_ms=duration_ms,
            )

        # Scan from bottom for the first line that parses as JSON
        output = None
        json_error = None
        for line in reversed(stdout.split("\n")):
            line = line.strip()
            if not line:
                continue
            try:
                output = json.loads(line)
                break
            except json.JSONDecodeError as e:
                if json_error is None:
                    json_error = f"Failed to parse JSON: {e}. Line: {line[:200]}"

        if output is None:
            return ExecutionResult(
                success=False,
                stdout=result.stdout,
                stderr=result.stderr,
                error_type="json_parse",
                error_message=json_error or "No JSON found in output",
                duration_ms=duration_ms,
            )

        return ExecutionResult(
            success=True,
            output=output,
            stdout=result.stdout,
            stderr=result.stderr,
            duration_ms=duration_ms,
        )

    except subprocess.TimeoutExpired:
        duration_ms = int((time.monotonic() - start) * 1000)
        return ExecutionResult(
            success=False,
            error_type="timeout",
            error_message=f"Script timed out after {config.timeout_seconds}s",
            duration_ms=duration_ms,
        )

    except Exception as e:
        duration_ms = int((time.monotonic() - start) * 1000)
        return ExecutionResult(
            success=False,
            error_type="runtime",
            error_message=str(e),
            duration_ms=duration_ms,
            traceback=traceback.format_exc(),
        )

    finally:
        # Clean up temp file
        try:
            Path(temp_path).unlink()
        except OSError:
            pass
