"""Deterministic local tools used by the workflow and agent demos."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from langchain.tools import tool
from langsmith import traceable

from src.schemas import ToolResult
from src.utils import read_json

DATA_DIR = Path(__file__).resolve().parent.parent / "data"
KB_ARTICLES = read_json(DATA_DIR / "kb_articles.json")
POLICIES = {item["name"]: item for item in read_json(DATA_DIR / "policies.json")}
ORDERS = {item["order_id"]: item for item in read_json(DATA_DIR / "orders.json")}
ACCOUNTS = {item["account_id"]: item for item in read_json(DATA_DIR / "accounts.json")}


def _result(tool_name: str, payload: dict[str, Any], ok: bool = True) -> dict[str, Any]:
    """Build a normalized tool result dictionary."""
    return ToolResult(tool_name=tool_name, ok=ok, payload=payload).model_dump()


@traceable(name="search_kb", run_type="tool")
def search_kb(query: str) -> dict[str, Any]:
    """Search the local support knowledge base with deterministic keyword matching.

    Use this tool when:
    - you need general support guidance, troubleshooting steps, or policy-adjacent help text
    - the ticket asks a broad "how does this work?" question
    - you want background context before drafting a customer response

    Args:
    - query: Free-text search query describing the customer issue or question.

    Do not use this tool when:
    - you already know the exact policy name and should call `get_policy` instead
    - you need a specific order lookup and should call `get_order_status`
    - you need a specific account lookup and should call `get_account_status`
    """
    query_lc = query.lower()
    scored: list[tuple[int, dict[str, Any]]] = []
    for article in KB_ARTICLES:
        haystack = " ".join(
            [article["title"], article["content"], " ".join(article["tags"])]
        ).lower()
        score = sum(1 for token in query_lc.split() if token in haystack)
        scored.append((score, article))

    top_score, top_article = max(scored, key=lambda item: item[0])
    return _result(
        "search_kb",
        {
            "query": query,
            "match_score": top_score,
            "article": top_article if top_score > 0 else None,
        },
        ok=top_score > 0,
    )


@traceable(name="get_policy", run_type="tool")
def get_policy(policy_name: str) -> dict[str, Any]:
    """Fetch a named business policy from the local policy store.

    Use this tool when:
    - you need the exact refund, cancellation, billing, damage, or account policy
    - the workflow or agent has already identified the relevant policy name
    - you want structured policy rules rather than fuzzy knowledge-base guidance

    Args:
    - policy_name: Exact policy identifier such as `refund_policy` or `cancellation_policy`.

    Do not use this tool when:
    - you do not know which policy is relevant yet and should search the KB first
    - you need live order facts and should call `get_order_status`
    - you need live account facts and should call `get_account_status`
    """
    policy = POLICIES.get(policy_name)
    return _result(
        "get_policy",
        {"policy_name": policy_name, "policy": policy},
        ok=policy is not None,
    )


@traceable(name="get_order_status", run_type="tool")
def get_order_status(order_id: str) -> dict[str, Any]:
    """Fetch order status details from the local order store.

    Use this tool when:
    - the ticket includes a known order ID
    - you need shipment, delivery, ETA, or payment status for a specific order
    - you need order facts before deciding whether to escalate

    Args:
    - order_id: Order identifier such as `1234`.

    Do not use this tool when:
    - no order ID is available yet and you should ask the customer for it
    - the task is purely policy explanation and `get_policy` is enough
    - the issue is account-focused and should use `get_account_status`
    """
    order = ORDERS.get(order_id)
    return _result(
        "get_order_status",
        {"order_id": order_id, "order": order},
        ok=order is not None,
    )


@traceable(name="get_account_status", run_type="tool")
def get_account_status(account_id: str) -> dict[str, Any]:
    """Fetch account and subscription details from the local account store.

    Use this tool when:
    - the ticket is about login, account access, subscription state, or renewal status
    - you have a known account ID
    - you need account facts before deciding whether to escalate

    Args:
    - account_id: Account identifier such as `A-101`.

    Do not use this tool when:
    - no account ID is available yet and you should ask the customer for it
    - the task is purely order-focused and should use `get_order_status`
    - the task is purely general guidance and should use `search_kb`
    """
    account = ACCOUNTS.get(account_id)
    return _result(
        "get_account_status",
        {"account_id": account_id, "account": account},
        ok=account is not None,
    )


@traceable(name="create_escalation", run_type="tool")
def create_escalation(ticket_id: str, reason: str) -> dict[str, Any]:
    """Create a mock escalation record for cases that need human review.

    Use this tool when:
    - the case clearly needs a human handoff
    - there is billing risk, account risk, safety risk, or an exception request
    - you already have enough evidence to justify escalation

    Args:
    - ticket_id: Support ticket identifier such as `T-101`.
    - reason: Short explanation for why the case is being escalated.

    Do not use this tool when:
    - the issue can be resolved with a normal response and no handoff
    - you are still missing critical facts and should ask a follow-up question first
    - you only need policy or status context and should use the lookup tools instead
    """
    return _result(
        "create_escalation",
        {
            "ticket_id": ticket_id,
            "escalation_id": f"ESC-{ticket_id}",
            "reason": reason,
            "status": "created",
        },
    )


LANGCHAIN_TOOLS = [
    tool(search_kb),
    tool(get_policy),
    tool(get_order_status),
    tool(get_account_status),
    tool(create_escalation),
]

TOOLS_BY_NAME = {tool_.name: tool_ for tool_ in LANGCHAIN_TOOLS}


def normalize_tool_payload(tool_output: dict[str, Any]) -> dict[str, Any]:
    """Simplify a tool result for notebook-friendly display."""
    return {
        "tool": tool_output["tool_name"],
        "ok": tool_output["ok"],
        "payload": tool_output["payload"],
    }
