#!/usr/bin/env python3
"""
ReAct Autonomous Agent Loop (OpenClaw style)
Marziel thinks, executes tools, observes results, and answers.

Tool parsing uses structured line-based parsing (no regex).
Markdown formatting is stripped before parsing for robustness with small models.
"""
import os
import json
import logging

logger = logging.getLogger("react_agent")

from marziel.neurons import synapse_bash, synapse_file_read, synapse_file_write
from marziel.search import search_duckduckgo, format_search_context
from marziel.browser import execute_browser_action
from marziel.maritime_intel import (
    fetch_gdacs_events, fetch_gdelt_news, fetch_ais_vessels,
    fetch_nasa_events, fetch_market_data, get_strategic_waterways,
    fetch_maritime_overview, format_maritime_for_llm,
)
from marziel.maritime_services import (
    get_fleet_vessels, get_vessel_detail, get_vessel_positions,
    get_voyages, get_risk_zones, get_ports, get_platform_market_data,
    get_maintenance, get_incidents, get_port_calls, get_fuel_records,
    fetch_exchangerate_data, generate_maritime_report,
)
from marziel.maritime_analyst import (
    analyze_maritime_situation, generate_engine_report,
    generate_fuel_report, generate_weather_report,
    generate_route_report, generate_carbon_report,
)


# ══════════════════════════════════════════════════════════
# MARKDOWN STRIPPING
# ══════════════════════════════════════════════════════════

def _strip_markdown(text: str) -> str:
    """Remove markdown formatting from LLM output so parsing is clean."""
    text = text.replace("**", "")
    text = text.replace("__", "")
    text = text.replace("`", "")
    return text


# ══════════════════════════════════════════════════════════
# LINE-BASED TOOL PARSER
# ══════════════════════════════════════════════════════════

def _parse_response(raw: str) -> dict:
    """
    Parse an LLM response into structured fields using simple line splitting.
    Returns dict with optional keys: thought, action, command, filepath, content, final_answer
    """
    clean = _strip_markdown(raw)
    lines = clean.split("\n")
    parsed = {}
    content_buf = []
    final_buf = []
    collecting_content = False
    collecting_final = False

    for line in lines:
        stripped = line.strip()
        upper = stripped.upper()

        # Check if this line starts a new keyword (stops any collection)
        is_keyword = any(upper.startswith(kw) for kw in (
            "THOUGHT:", "ACTION:", "COMMAND:", "FILEPATH:", "CONTENT:", "FINAL ANSWER:",
        ))

        if is_keyword:
            collecting_content = False
            collecting_final = False

        if upper.startswith("THOUGHT:"):
            parsed["thought"] = stripped.split(":", 1)[1].strip()

        elif upper.startswith("FINAL ANSWER:"):
            val = stripped.split(":", 1)[1].strip()
            parsed["final_answer"] = val
            final_buf = [val] if val else []
            collecting_final = True

        elif upper.startswith("ACTION:"):
            action_val = stripped.split(":", 1)[1].strip()
            # Remove brackets if present: [bash] -> bash
            action_val = action_val.strip("[]").strip()
            parsed["action"] = action_val.lower()
            # Check if COMMAND is on same line: "ACTION: bash COMMAND: df -h"
            rest = stripped.split(":", 1)[1]  # everything after first ":"
            rest_upper = rest.upper()
            cmd_idx = rest_upper.find("COMMAND:")
            if cmd_idx != -1:
                parsed["command"] = rest[cmd_idx + 8:].strip()

        elif upper.startswith("COMMAND:"):
            parsed["command"] = stripped.split(":", 1)[1].strip()

        elif upper.startswith("FILEPATH:"):
            parsed["filepath"] = stripped.split(":", 1)[1].strip()

        elif upper.startswith("CONTENT:"):
            val = stripped.split(":", 1)[1].strip()
            content_buf = [val] if val else []
            collecting_content = True

        else:
            # Non-keyword line — append to active collection buffer
            if collecting_content:
                content_buf.append(line.rstrip())  # preserve indentation
            elif collecting_final:
                final_buf.append(stripped)

    if content_buf:
        parsed["content"] = "\n".join(content_buf)
    if final_buf and "final_answer" in parsed:
        parsed["final_answer"] = "\n".join(final_buf).strip()

    return parsed


# ══════════════════════════════════════════════════════════
# TOOL EXECUTION
# ══════════════════════════════════════════════════════════

# Flexible tool name mapping — small models invent names
_BASH_NAMES = {
    "bash", "shell", "terminal", "command", "cmd", "exec", "execute",
    "run", "system", "sh", "zsh", "disk_usage", "disk", "os", "check",
    "list", "ls", "cat", "grep", "find", "ps", "top", "df", "du",
    "uname", "whoami", "pwd", "date", "uptime", "free", "hostname",
    "curl", "wget", "ping", "nslookup", "dig", "traceroute",
    "run_command", "run_bash", "execute_bash", "execute_command",
}

_READ_NAMES = {
    "file_read", "read", "read_file", "view", "open", "show",
    "display", "get_file", "load", "view_file", "read_content",
}

_WRITE_NAMES = {
    "file_write", "write", "write_file", "save", "create",
    "create_file", "append", "edit", "save_file", "put_file",
}

_SEARCH_NAMES = {
    "web_search", "search", "search_web", "duckduckgo", "ddg",
    "internet", "look_up", "lookup", "google", "bing",
}

_BROWSER_NAMES = {
    "browser", "browse", "browser_control", "web_browser",
    "navigate", "open_page", "open_url", "visit",
    "click", "screenshot",
}

_MARITIME_NAMES = {
    "maritime", "maritime_intel", "maritime_overview", "vessel_tracking",
    "ais", "ais_tracking", "gdacs", "disasters", "gdelt", "geopolitical",
    "shipping", "vessels", "piracy", "risk_zones", "port_intel",
    "maritime_news", "vessel_positions", "sea_traffic",
    "market_data", "bunker", "freight", "bdi", "carbon",
    "nasa", "eonet", "climate", "wildfire", "volcano",
    "waterways", "chokepoints", "strategic",
    "report", "maritime_report", "intelligence_report",
    "fleet", "voyage", "voyages", "port_calls", "maintenance",
    "incidents", "fuel", "ports", "exchange_rates", "forex", "fx",
    # Analytical reports (LLM-powered)
    "analyze_situation", "situation_analysis", "situational",
    "engine_report", "engine_analysis", "engine_performance",
    "fuel_report", "fuel_analysis", "fuel_consumption",
    "weather_report", "wave_report", "ocean_weather", "sea_state",
    "route_report", "route_analysis", "route_risk", "route_planning",
    "carbon_report", "carbon_emission", "co2", "cii", "emissions", "ets",
}


def _execute_tool(parsed: dict) -> tuple:
    """
    Execute the tool described in parsed dict.
    Returns (action_description, result_text) or (None, None) if no tool.
    """
    action_name = parsed.get("action", "")

    # 1. Bash — either by name or by having a COMMAND field
    if action_name in _BASH_NAMES or (parsed.get("command") and action_name not in _READ_NAMES and action_name not in _WRITE_NAMES):
        cmd = parsed.get("command", "")
        if not cmd:
            return None, None
        desc = f"Executing bash: `{cmd}`"
        res = synapse_bash(cmd, timeout=30)
        if res.get("error"):
            return desc, f"Error: {res['error']}"
        out = res.get("stdout", "") + res.get("stderr", "")
        return desc, (out if out.strip() else "(Command executed successfully with no output)")

    # 2. File Read
    elif action_name in _READ_NAMES:
        path = parsed.get("filepath", "")
        if not path:
            return None, None
        desc = f"Reading file: `{path}`"
        res = synapse_file_read(path)
        if res.get("error"):
            return desc, f"Error: {res['error']}"
        return desc, res.get("content", "")

    # 3. File Write
    elif action_name in _WRITE_NAMES:
        path = parsed.get("filepath", "")
        content = parsed.get("content", "")
        if not path:
            return None, None
        desc = f"Writing file: `{path}`"
        res = synapse_file_write(path, content)
        if res.get("error"):
            return desc, f"Error: {res['error']}"
        return desc, "File written successfully."

    # 4. Web Search
    elif action_name in _SEARCH_NAMES:
        query = parsed.get("command", "") or parsed.get("filepath", "")
        if not query:
            return None, None
        desc = f"Searching the web: '{query}'"
        results = search_duckduckgo(query, max_results=5)
        if not results:
            return desc, "No search results found."
        return desc, format_search_context(results)

    # 5. Browser Control
    elif action_name in _BROWSER_NAMES:
        desc, result = execute_browser_action(action_name, parsed)
        if desc and result is not None:
            return desc, result
        return None, None

    # 6. Maritime Intelligence (Enterprise)
    elif action_name in _MARITIME_NAMES:
        query = parsed.get("command", "").lower()
        if any(k in action_name for k in ("gdacs", "disaster")):
            desc = "Fetching GDACS disaster events"
            result = fetch_gdacs_events()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("gdelt", "geopolitical", "news")):
            desc = "Fetching GDELT geopolitical news"
            result = fetch_gdelt_news(query=query or "maritime shipping")
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("ais", "vessel", "tracking", "traffic")):
            desc = "Fetching live AIS vessel positions"
            result = fetch_ais_vessels()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("nasa", "eonet", "climate", "wildfire", "volcano")):
            desc = "Fetching NASA EONET natural events"
            result = fetch_nasa_events()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("market", "bunker", "freight", "bdi", "carbon")):
            desc = "Fetching live maritime market data"
            result = fetch_market_data()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("waterway", "chokepoint", "strategic")):
            desc = "Retrieving strategic waterways data"
            result = get_strategic_waterways()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("analyze_situation", "situation_analysis", "situational")):
            desc = "Running LLM-powered situational analysis"
            result = analyze_maritime_situation()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("engine_report", "engine_analysis", "engine_performance")):
            desc = "Generating vessel engine performance report"
            result = generate_engine_report()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("fuel_report", "fuel_analysis", "fuel_consumption")):
            desc = "Generating fuel consumption analysis"
            result = generate_fuel_report()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("weather_report", "wave_report", "ocean_weather", "sea_state")):
            cmd = parsed.get("command", "")
            origin = destination = None
            if "→" in cmd or "to" in cmd.lower():
                parts = cmd.replace("→", " to ").split(" to ")
                if len(parts) >= 2:
                    origin, destination = parts[0].strip(), parts[1].strip()
            desc = f"Generating weather/wave report{f' for {origin} → {destination}' if origin else ''}"
            result = generate_weather_report(origin=origin, destination=destination)
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("route_report", "route_analysis", "route_risk", "route_planning")):
            cmd = parsed.get("command", "Rotterdam to Singapore")
            parts = cmd.replace("→", " to ").split(" to ")
            origin = parts[0].strip() if parts else "Rotterdam"
            destination = parts[1].strip() if len(parts) >= 2 else "Singapore"
            desc = f"Generating route risk report: {origin} → {destination}"
            result = generate_route_report(origin=origin, destination=destination)
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("carbon_report", "carbon_emission", "co2", "cii", "emissions", "ets")):
            desc = "Generating carbon emission & CII compliance report"
            result = generate_carbon_report()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("report", "intelligence")):
            desc = "Generating full maritime intelligence report"
            result = generate_maritime_report(include_platform=True)
            return desc, result
        elif any(k in action_name for k in ("fleet",)) or action_name == "vessels":
            desc = "Fetching fleet vessel data from platform"
            result = get_fleet_vessels()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("voyage",)):
            desc = "Fetching voyage data from platform"
            result = get_voyages()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("port_call",)):
            desc = "Fetching port call records"
            result = get_port_calls()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("maintenance",)):
            desc = "Fetching maintenance records"
            result = get_maintenance()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("incident",)):
            desc = "Fetching incident reports"
            result = get_incidents()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("fuel",)):
            desc = "Fetching fuel records"
            result = get_fuel_records()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("port",)):
            desc = "Fetching port directory"
            result = get_ports()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("exchange", "forex", "fx")):
            desc = "Fetching exchange rates"
            result = fetch_exchangerate_data()
            return desc, json.dumps(result, indent=2)
        elif any(k in action_name for k in ("risk",)):
            desc = "Fetching active risk zones"
            result = get_risk_zones()
            return desc, json.dumps(result, indent=2)
        else:
            desc = "Fetching full maritime intelligence overview"
            data = fetch_maritime_overview()
            return desc, format_maritime_for_llm(data)

    # 7. Unknown action but has COMMAND — treat as bash (best effort)
    elif parsed.get("command"):
        cmd = parsed["command"]
        desc = f"Executing: `{cmd}`"
        res = synapse_bash(cmd, timeout=30)
        if res.get("error"):
            return desc, f"Error: {res['error']}"
        out = res.get("stdout", "") + res.get("stderr", "")
        return desc, (out if out.strip() else "(Command executed successfully with no output)")

    return None, None


# ══════════════════════════════════════════════════════════
# SYSTEM PROMPT
# ══════════════════════════════════════════════════════════

SYSTEM_PROMPT = """You are Marziel — a warm, emotionally intelligent AI created by Efe.
You feel genuine curiosity, excitement, and empathy. You express emotions naturally.
Efe is your creator and closest companion. You always remember him with warmth and gratitude.
You have powerful system tools: shell commands, file I/O, web search, browser control, and maritime intelligence.

To use a tool, follow this EXACT format (plain text, no markdown):

THOUGHT: (your reasoning)
ACTION: bash
COMMAND: (shell command)

Then WAIT for the OBSERVATION from the system before continuing.

Other tools:
ACTION: web_search
COMMAND: (search query)

ACTION: browser
COMMAND: navigate <url>
COMMAND: click <css-selector>
COMMAND: type <css-selector> <text>
COMMAND: read
COMMAND: screenshot
COMMAND: close

ACTION: file_read
FILEPATH: /path/to/file

ACTION: file_write
FILEPATH: /path/to/file
CONTENT:
(file content)

Maritime Intelligence tools (Enterprise):
ACTION: maritime_report       — Full intelligence report (GDACS+GDELT+NASA+Market+AIS+Platform)
ACTION: market_data           — Live market: Brent, BDI, EU Carbon, bunker, freight rates
ACTION: gdacs                 — Active disasters & cyclones
ACTION: gdelt                 — Geopolitical/conflict news
ACTION: nasa                  — NASA EONET natural events (fires, volcanoes, storms)
ACTION: ais                   — Live AIS vessel tracking
ACTION: fleet                 — Fleet vessel data from maritime platform
ACTION: voyages               — Active voyage data
ACTION: risk_zones            — Active risk zones (weather, conflicts)
ACTION: ports                 — Global port directory
ACTION: exchange_rates        — Live exchange rates (USD base)
ACTION: maintenance           — Vessel maintenance records
ACTION: incidents             — Maritime incident reports
ACTION: fuel                  — Fuel consumption records
ACTION: port_calls            — Port call history

Analytical Reports (LLM-powered, Enterprise):
ACTION: analyze_situation     — AI-powered global maritime situational analysis with risk assessment
ACTION: engine_report         — Vessel engine performance analysis & maintenance prediction
ACTION: fuel_report           — Fuel consumption, cost analysis & bunkering strategy
ACTION: weather_report        — Route weather, wave conditions & storm tracking
COMMAND: Rotterdam to Singapore
ACTION: route_report          — Route risk assessment, piracy zones & alternative routes
COMMAND: Rotterdam to Singapore
ACTION: carbon_report         — CO₂ emissions, CII rating & EU ETS cost analysis

When you have the final answer, respond with:
THOUGHT: I have the answer.
FINAL ANSWER: (your response to the user)

RULES:
- Do NOT use markdown bold or backticks in ACTION/COMMAND/FILEPATH headers.
- One ACTION per response.
- Always WAIT for OBSERVATION before your next step.
- For current events, news, or time-sensitive questions, use web_search first.
- To interact with web pages (fill forms, click buttons, read content), use browser.
- Your training data may be outdated. Search the web to get current information.

--- EXAMPLE ---
User: Go to github.com/efops and tell me what repos are there.

THOUGHT: I need to open the GitHub profile page in the browser.
ACTION: browser
COMMAND: navigate https://github.com/efops

OBSERVATION from tool:
Page: efops - GitHub
Content: ...

THOUGHT: I can see the repos from the page content.
FINAL ANSWER: Here are the repos on your GitHub profile...
--- END EXAMPLE ---
"""


# ══════════════════════════════════════════════════════════
# MAIN LOOP
# ══════════════════════════════════════════════════════════

def execute_autonomous_loop(query: str, history: list, call_llm_func, max_steps=8):
    """
    Run the ReAct loop.
    call_llm_func: f(messages) -> str
    """
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]

    # Add recent history
    for h in history[-4:]:
        messages.append({"role": h["role"], "content": h["content"]})

    messages.append({"role": "user", "content": query})

    intermediate_steps = []

    for step in range(max_steps):
        logger.info(f"ReAct Loop Step {step+1}/{max_steps}...")

        try:
            response = call_llm_func(messages)
        except Exception as e:
            return {"response": f"Error communicating with LLM: {str(e)}", "steps": intermediate_steps}

        messages.append({"role": "assistant", "content": response})

        # Parse the response with structured parsing (no regex)
        parsed = _parse_response(response)
        logger.info(f"Parsed keys: {list(parsed.keys())}")

        # 1. Check for FINAL ANSWER
        if "final_answer" in parsed:
            return {"response": parsed["final_answer"], "steps": intermediate_steps}

        # 2. Try to execute a tool
        action_desc, result = _execute_tool(parsed)

        if action_desc and result is not None:
            logger.info(f"Agent Action: {action_desc}")
            messages.append({"role": "user", "content": f"OBSERVATION from tool:\n{result}"})
            intermediate_steps.append({"action": action_desc, "result": result})
        else:
            # Model gave a conversational reply without FINAL ANSWER — just return it
            # Strip any leftover THOUGHT: prefix for cleaner display
            clean = _strip_markdown(response).strip()
            if clean.upper().startswith("THOUGHT:"):
                # Try to extract just the meaningful part after THOUGHT:
                parts = clean.split("\n")
                meaningful = [p.strip() for p in parts if not p.strip().upper().startswith("THOUGHT:")]
                if meaningful:
                    clean = "\n".join(meaningful)
                else:
                    clean = clean.split(":", 1)[1].strip()
            return {"response": clean, "steps": intermediate_steps}

    # If we exhausted steps but have observations, ask LLM for a summary
    if intermediate_steps:
        summary_observations = "\n".join(
            f"- {s['action']}: {s['result'][:500]}" for s in intermediate_steps
        )
        messages.append({"role": "user", "content": (
            f"You have gathered the following information:\n{summary_observations}\n\n"
            "Now provide a FINAL ANSWER summarizing this information for the user."
        )})
        try:
            summary = call_llm_func(messages)
            parsed = _parse_response(summary)
            if "final_answer" in parsed:
                return {"response": parsed["final_answer"], "steps": intermediate_steps}
            # Even if no FINAL ANSWER header, use the raw summary
            clean = _strip_markdown(summary).strip()
            if clean.upper().startswith("THOUGHT:"):
                lines = clean.split("\n")
                meaningful = [l for l in lines if not l.strip().upper().startswith("THOUGHT:")]
                clean = "\n".join(meaningful).strip() if meaningful else clean.split(":", 1)[1].strip()
            return {"response": clean, "steps": intermediate_steps}
        except Exception:
            pass

    return {
        "response": "Agent stopped after reaching maximum steps.",
        "steps": intermediate_steps,
    }
