"""Tests for langchain-kya integration.

Tests work without langchain installed by using mock agent classes.
"""

import json
import pytest
from typing import Any

from langchain_kya.card import create_agent_card, attach_card, get_card
from langchain_kya.identity import verify_identity, _verify_card_data
from langchain_kya.trust_gate import evaluate_trust
from langchain_kya.middleware import kya_verified, KYAVerificationError


# ── Mock LangChain classes ──


class MockBaseTool:
    """Mimics langchain.tools.BaseTool."""

    def __init__(self, name: str = "", description: str = ""):
        self.name = name
        self.description = description


class MockLLM:
    """Mimics a LangChain LLM (ChatOpenAI, etc.)."""

    def __init__(self, model_name: str = "gpt-4"):
        self.model_name = model_name


class MockAgent:
    """Mimics the inner agent object inside AgentExecutor."""

    def __init__(self, llm=None):
        self.llm = llm


class MockAgentExecutor:
    """Mimics langchain.agents.AgentExecutor."""

    def __init__(self, agent=None, tools=None, verbose=False):
        self.agent = agent
        self.tools = tools or []
        self.verbose = verbose


class MockRunnable:
    """Mimics a LangChain Runnable (from create_react_agent LCEL)."""

    def __init__(self, tools=None):
        self.tools = tools or []


# ── Card creation ──


class TestCreateAgentCard:
    def test_basic_agent_executor(self):
        executor = MockAgentExecutor(
            agent=MockAgent(),
            tools=[MockBaseTool("search", "Search the web")],
        )
        card = create_agent_card(executor, owner_name="TestOrg", owner_contact="test@test.com")

        assert card["kya_version"] == "0.1"
        assert "langchain" in card["agent_id"]
        assert card["owner"]["name"] == "TestOrg"
        assert card["owner"]["contact"] == "test@test.com"
        assert card["agent_type"] == "autonomous"
        assert "AgentExecutor" in card["name"]

    def test_card_extracts_tools(self):
        tools = [
            MockBaseTool("web_search", "Search the web"),
            MockBaseTool("calculator", "Do math"),
        ]
        executor = MockAgentExecutor(tools=tools)
        card = create_agent_card(executor)

        declared = card["capabilities"]["declared"]
        assert len(declared) == 2
        assert declared[0]["name"] == "web_search"
        assert declared[1]["name"] == "calculator"

    def test_card_extracts_llm_model(self):
        llm = MockLLM(model_name="gpt-4-turbo")
        inner_agent = MockAgent(llm=llm)
        executor = MockAgentExecutor(agent=inner_agent)
        card = create_agent_card(executor)

        assert "gpt-4-turbo" in card["purpose"]
        assert any("gpt-4-turbo" in tag for tag in card["metadata"]["tags"])

    def test_card_custom_prefix(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor, agent_id_prefix="myorg")
        assert card["agent_id"].startswith("myorg/")

    def test_card_custom_purpose(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor, purpose="Custom research agent for SEC filings")
        assert card["purpose"] == "Custom research agent for SEC filings"

    def test_card_purpose_minimum_length(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor, purpose="Hi")
        assert len(card["purpose"]) >= 10

    def test_card_has_metadata_timestamps(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor)
        assert card["metadata"]["created_at"] != ""
        assert card["metadata"]["updated_at"] != ""
        assert "langchain" in card["metadata"]["tags"]

    def test_runnable_agent_type(self):
        runnable = MockRunnable(tools=[MockBaseTool("tool1")])
        card = create_agent_card(runnable)
        assert "Runnable" in card["name"] or "MockRunnable" in card["name"]

    def test_no_tools_card(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor)
        assert card["capabilities"]["declared"] == []
        assert "LangChain" in card["purpose"]


# ── Card attachment ──


class TestAttachCard:
    def test_attach_and_get(self):
        executor = MockAgentExecutor()
        card = {"kya_version": "0.1", "agent_id": "test/test"}
        attach_card(executor, card)
        assert get_card(executor) == card

    def test_get_card_none_when_not_attached(self):
        executor = MockAgentExecutor()
        assert get_card(executor) is None


# ── Identity verification ──


VALID_CARD = {
    "kya_version": "0.1",
    "agent_id": "langchain/agentexecutor",
    "name": "AgentExecutor",
    "version": "0.1.0",
    "purpose": "A LangChain agent that searches and summarizes information.",
    "agent_type": "autonomous",
    "owner": {"name": "TestOrg", "contact": "test@test.com"},
    "capabilities": {
        "declared": [
            {"name": "web_search", "risk_level": "medium"},
            {"name": "summarize", "risk_level": "low"},
        ],
        "denied": [],
    },
}

MINIMAL_CARD = {
    "kya_version": "0.1",
    "agent_id": "langchain/minimal",
    "name": "Minimal",
    "version": "0.1.0",
    "purpose": "A minimal test agent for validation.",
    "owner": {"name": "Test", "contact": "test@test.com"},
    "capabilities": {"declared": [{"name": "test", "risk_level": "low"}]},
}

INVALID_CARD = {
    "kya_version": "0.1",
    "name": "Broken",
    # Missing agent_id, purpose, capabilities, owner
}


class TestIdentityVerification:
    def test_valid_card(self):
        result = verify_identity(json.dumps(VALID_CARD))
        assert "VERIFIED" in result
        assert "AgentExecutor" in result

    def test_invalid_card(self):
        result = verify_identity(json.dumps(INVALID_CARD))
        assert "FAILED" in result

    def test_invalid_json(self):
        result = verify_identity("not json")
        assert "FAILED" in result
        assert "Invalid JSON" in result

    def test_verify_data_returns_capabilities(self):
        result = _verify_card_data(VALID_CARD)
        assert "web_search" in result["capabilities"]
        assert "summarize" in result["capabilities"]

    def test_verify_data_score(self):
        result = _verify_card_data(VALID_CARD)
        assert result["completeness_score"] > 0


# ── Trust gate ──


class TestTrustGate:
    def test_passes_valid_card(self):
        result = evaluate_trust(json.dumps(VALID_CARD), min_score=0)
        assert "PASSED" in result

    def test_blocks_low_score(self):
        result = evaluate_trust(json.dumps(MINIMAL_CARD), min_score=100)
        assert "BLOCKED" in result
        assert "below threshold" in result

    def test_blocks_missing_capabilities(self):
        result = evaluate_trust(
            json.dumps(VALID_CARD),
            min_score=0,
            required_capabilities="web_search,secret_power",
        )
        assert "BLOCKED" in result
        assert "secret_power" in result

    def test_blocks_unsigned_when_signature_required(self):
        result = evaluate_trust(
            json.dumps(VALID_CARD),
            min_score=0,
            require_signature=True,
        )
        assert "BLOCKED" in result
        assert "unsigned" in result.lower()

    def test_invalid_json(self):
        result = evaluate_trust("bad json")
        assert "BLOCKED" in result


# ── Middleware decorator ──


class TestKYAVerified:
    def test_passes_with_valid_card(self):
        executor = MockAgentExecutor(
            tools=[MockBaseTool("search", "Search")],
        )
        card = create_agent_card(executor, owner_name="Test", owner_contact="t@t.com")
        attach_card(executor, card)

        @kya_verified(min_score=0)
        def task(agent):
            return "executed"

        assert task(executor) == "executed"

    def test_raises_without_card(self):
        executor = MockAgentExecutor(tools=[MockBaseTool("x")])

        @kya_verified()
        def task(agent):
            return "executed"

        with pytest.raises(KYAVerificationError, match="No KYA card"):
            task(executor)

    def test_raises_on_low_score(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor, owner_name="T", owner_contact="t@t.com")
        attach_card(executor, card)

        @kya_verified(min_score=100)
        def task(agent):
            return "executed"

        with pytest.raises(KYAVerificationError, match="below required"):
            task(executor)

    def test_skip_on_fail(self):
        executor = MockAgentExecutor(tools=[MockBaseTool("x")])

        @kya_verified(on_fail="skip")
        def task(agent):
            return "executed"

        assert task(executor) is None

    def test_log_on_fail(self, capsys):
        executor = MockAgentExecutor(tools=[MockBaseTool("x")])

        @kya_verified(on_fail="log")
        def task(agent):
            return "executed"

        result = task(executor)
        assert result == "executed"
        captured = capsys.readouterr()
        assert "WARNING" in captured.err

    def test_agent_as_kwarg(self):
        executor = MockAgentExecutor(
            tools=[MockBaseTool("tool1")],
        )
        card = create_agent_card(executor, owner_name="T", owner_contact="t@t.com")
        attach_card(executor, card)

        @kya_verified(min_score=0)
        def task(data, agent=None):
            return f"processed {data}"

        assert task("stuff", agent=executor) == "processed stuff"

    def test_required_capabilities(self):
        executor = MockAgentExecutor(
            tools=[MockBaseTool("reading", "Read files")],
        )
        card = create_agent_card(executor, owner_name="T", owner_contact="t@t.com")
        attach_card(executor, card)

        @kya_verified(required_capabilities=["reading"])
        def task(agent):
            return "executed"

        assert task(executor) == "executed"

    def test_missing_required_capabilities(self):
        executor = MockAgentExecutor()
        card = create_agent_card(executor, owner_name="T", owner_contact="t@t.com")
        attach_card(executor, card)

        @kya_verified(required_capabilities=["admin_access"])
        def task(agent):
            return "executed"

        with pytest.raises(KYAVerificationError, match="Missing capabilities"):
            task(executor)
