"""Tests for actiongate-langchain integration."""

from __future__ import annotations

import sys
import traceback
from decimal import Decimal

sys.path.insert(0, ".")

passed = 0
failed = 0
errors: list[str] = []

def test(name):
    def decorator(fn):
        global passed, failed
        try:
            fn()
            passed += 1
            print(f"  PASS  {name}")
        except Exception as e:
            failed += 1
            errors.append(f"  FAIL  {name}: {e}\n{traceback.format_exc()}")
            print(f"  FAIL  {name}: {e}")
        return fn
    return decorator

_clk = 1.0
def fake_clock():
    return _clk


print("── GatedTool.from_function ──")

@test("allowed call returns result")
def _():
    from actiongate import Engine, Gate, Policy
    from actiongate_langchain import GatedTool

    ag = Engine(clock=fake_clock)
    tool = GatedTool.from_function(
        func=lambda query: f"results for {query}",
        name="search", description="Search the web",
        actiongate=ag,
        gate=Gate("test", "search"),
        policy=Policy(max_calls=5, window=60),
    )
    result = tool.invoke({"query": "hello"})
    assert result == "results for hello", f"got {result}"


@test("rate limited raises ToolException")
def _():
    global _clk
    _clk = 1.0
    from actiongate import Engine, Gate, Policy
    from langchain_core.tools import ToolException
    from actiongate_langchain import GatedTool

    ag = Engine(clock=fake_clock)
    tool = GatedTool.from_function(
        func=lambda: "pong",
        name="ping", description="Ping",
        actiongate=ag,
        gate=Gate("test", "ping"),
        policy=Policy(max_calls=1, window=60),
    )
    tool.handle_tool_error = True
    tool.invoke({})

    _clk = 2.0
    result = tool.invoke({})
    assert "actiongate" in str(result).lower(), f"expected actiongate block, got {result}"


@test("budget exceeded raises ToolException")
def _():
    global _clk
    _clk = 1.0
    from actiongate import Engine as AG, Gate, Policy
    from budgetgate import Engine as BG, Ledger, Budget
    from actiongate_langchain import GatedTool

    ag = AG(clock=fake_clock)
    bg = BG(clock=fake_clock)
    tool = GatedTool.from_function(
        func=lambda: "done",
        name="expensive", description="Costly op",
        actiongate=ag,
        gate=Gate("test", "exp"),
        policy=Policy(max_calls=100, window=60),
        budgetgate=bg,
        ledger=Ledger("test", "api", "user:1"),
        budget=Budget(max_spend=Decimal("1.00"), window=60),
        cost=Decimal("0.60"),
    )
    tool.handle_tool_error = True
    tool.invoke({})

    _clk = 2.0
    result = tool.invoke({})
    assert "budgetgate" in str(result).lower(), f"expected budget block, got {result}"


@test("policy violation raises ToolException")
def _():
    global _clk
    _clk = 1.0
    from actiongate import Engine as AG, Gate, Policy
    from rulegate import Engine as RG, Rule, Ruleset, Context
    from actiongate_langchain import GatedTool

    def no_bad(ctx: Context) -> bool:
        return "bad" not in str(ctx.kwargs).lower()

    ag = AG(clock=fake_clock)
    rg = RG(clock=fake_clock)
    tool = GatedTool.from_function(
        func=lambda query: f"results for {query}",
        name="search", description="Search",
        actiongate=ag,
        gate=Gate("test", "search2"),
        policy=Policy(max_calls=100, window=60),
        rulegate=rg,
        rule=Rule("test", "search"),
        ruleset=Ruleset(predicates=(no_bad,)),
    )
    tool.handle_tool_error = True

    result = tool.invoke({"query": "bad stuff"})
    assert "rulegate" in str(result).lower(), f"expected rule block, got {result}"


print("\n── @gated_tool decorator ──")

@test("decorator creates working tool")
def _():
    global _clk
    _clk = 1.0
    from actiongate import Engine, Gate, Policy
    from actiongate_langchain import gated_tool

    ag = Engine(clock=fake_clock)

    @gated_tool(
        actiongate=ag,
        gate=Gate("test", "dec"),
        policy=Policy(max_calls=5, window=60),
    )
    def my_search(query: str) -> str:
        """Search for things."""
        return f"found: {query}"

    assert my_search.name == "my_search"
    assert my_search.description == "Search for things."
    result = my_search.invoke({"query": "test"})
    assert result == "found: test"


@test("decorator preserves function name and docstring")
def _():
    from actiongate_langchain import gated_tool

    @gated_tool()
    def calculate_tax(amount: float, rate: float) -> float:
        """Calculate tax on an amount."""
        return amount * rate

    assert calculate_tax.name == "calculate_tax"
    assert calculate_tax.description == "Calculate tax on an amount."


print(f"\n{'═' * 50}")
print(f"Results: {passed} passed, {failed} failed")
if errors:
    print("\nFailures:")
    for e in errors:
        print(e)
print(f"{'═' * 50}")
sys.exit(1 if failed else 0)
