"""tools.py, the actual work an agent's tools do (plain Python functions).

A "tool" is just a normal function the model can ask you to run. Keeping them here,
separate from the agent wiring, makes the point: tools are ordinary code you can test
on their own, and you should, because the agent is only as good as its tools.

Everything here is safe and self-contained (no network, no real systems).
"""

import ast
import operator

# ---------- a SAFE calculator (never use eval() on model/user input) ---------
_OPS = {
    ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
    ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg,
}


def _eval(node):
    if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
        return node.value
    if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
        return _OPS[type(node.op)](_eval(node.left), _eval(node.right))
    if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
        return _OPS[type(node.op)](_eval(node.operand))
    raise ValueError("unsupported expression")


def calculate(expression: str) -> str:
    """Evaluate a basic arithmetic expression like '3 * (4 + 5)'. Safe: numbers and
    + - * / ** only, it parses the math instead of running it as code."""
    try:
        return str(_eval(ast.parse(expression, mode="eval").body))
    except Exception:
        return f"Could not calculate '{expression}'. Use numbers and + - * / ** only."


def count_characters(text: str) -> str:
    """A tiny custom tool: count the characters in some text."""
    return f"{len(text)} characters"


# ---------- a tiny notes store (memory the non-security helper can use) -------
_NOTES = []


def save_note(note: str) -> str:
    """Save a short note to remember for later."""
    _NOTES.append(note)
    return f"Saved. You now have {len(_NOTES)} note(s)."


def list_notes() -> str:
    """List all saved notes."""
    return "\n".join(f"- {n}" for n in _NOTES) if _NOTES else "No notes yet."


# ---------- SYNTHETIC security data (sample only, not real intel) ------------
# Educational, synthetic data. Never point these at real systems or real intel.
FAKE_THREAT_INTEL = {
    "185.220.101.45": "MALICIOUS, known Tor exit node, seen in credential-stuffing (synthetic).",
    "8.8.8.8":        "CLEAN, public DNS resolver (synthetic).",
    "evil-login.example": "MALICIOUS, phishing domain mimicking a login page (synthetic).",
    "a1b2c3d4e5":     "SUSPICIOUS, file hash flagged by 2/70 engines (synthetic).",
}

SAMPLE_LOGS = [
    "2026-06-01 09:12:03 login OK     user=alice   ip=10.0.0.5",
    "2026-06-01 09:14:55 login FAIL   user=admin   ip=185.220.101.45",
    "2026-06-01 09:14:58 login FAIL   user=admin   ip=185.220.101.45",
    "2026-06-01 09:15:02 login FAIL   user=admin   ip=185.220.101.45",
    "2026-06-01 09:15:09 login OK     user=admin   ip=185.220.101.45",
    "2026-06-01 09:20:41 download     user=admin   file=customer_db.csv ip=185.220.101.45",
    "2026-06-01 10:02:13 login OK     user=bob     ip=10.0.0.9",
]


def lookup_ioc(indicator: str) -> str:
    """Look up the reputation of an indicator (IP, domain, or file hash) in the
    (synthetic) threat-intel feed."""
    return FAKE_THREAT_INTEL.get(indicator.strip(), f"UNKNOWN, no record for '{indicator}' (synthetic).")


def search_logs(term: str) -> str:
    """Search the (synthetic) sample logs for lines containing a term (e.g. an IP or user)."""
    hits = [line for line in SAMPLE_LOGS if term.lower() in line.lower()]
    return "\n".join(hits) if hits else f"No log lines match '{term}'."
