"""Tests for the declarative handler system."""

import asyncio
import pytest
from shared.sx import parse, evaluate
from shared.sx.types import HandlerDef
from shared.sx.handlers import (
    register_handler,
    get_handler,
    get_all_handlers,
    clear_handlers,
)
from shared.sx.async_eval import async_eval, async_render
from shared.sx.primitives_io import RequestContext


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------

class TestHandlerRegistry:
    def setup_method(self):
        clear_handlers()

    def test_register_and_get(self):
        env = {}
        evaluate(parse("(defhandler test-card (&key slug) slug)"), env)
        handler = env["handler:test-card"]
        register_handler("blog", handler)
        assert get_handler("blog", "test-card") is handler

    def test_get_nonexistent(self):
        assert get_handler("blog", "nope") is None

    def test_get_all_handlers(self):
        env = {}
        evaluate(parse("(defhandler h1 (&key a) a)"), env)
        evaluate(parse("(defhandler h2 (&key b) b)"), env)
        register_handler("svc", env["handler:h1"])
        register_handler("svc", env["handler:h2"])
        all_h = get_all_handlers("svc")
        assert "h1" in all_h
        assert "h2" in all_h

    def test_clear_service(self):
        env = {}
        evaluate(parse("(defhandler h1 (&key a) a)"), env)
        register_handler("svc", env["handler:h1"])
        clear_handlers("svc")
        assert get_handler("svc", "h1") is None

    def test_clear_all(self):
        env = {}
        evaluate(parse("(defhandler h1 (&key a) a)"), env)
        register_handler("svc1", env["handler:h1"])
        register_handler("svc2", env["handler:h1"])
        clear_handlers()
        assert get_all_handlers("svc1") == {}
        assert get_all_handlers("svc2") == {}


# ---------------------------------------------------------------------------
# HandlerDef creation via evaluator
# ---------------------------------------------------------------------------

class TestHandlerDefCreation:
    def test_basic(self):
        env = {}
        evaluate(parse("(defhandler my-handler (&key id name) (str id name))"), env)
        h = env["handler:my-handler"]
        assert isinstance(h, HandlerDef)
        assert h.name == "my-handler"
        assert h.params == ["id", "name"]

    def test_no_params(self):
        env = {}
        evaluate(parse("(defhandler simple (&key) 42)"), env)
        h = env["handler:simple"]
        assert h.params == []

    def test_handler_closure_captures_env(self):
        env = {"x": 99}
        evaluate(parse("(defhandler uses-closure (&key) x)"), env)
        h = env["handler:uses-closure"]
        assert h.closure.get("x") == 99


# ---------------------------------------------------------------------------
# Async evaluator
# ---------------------------------------------------------------------------

class TestAsyncEval:
    def test_literals(self):
        ctx = RequestContext()
        assert asyncio.get_event_loop().run_until_complete(
            async_eval(parse("42"), {}, ctx)) == 42

    def test_let_and_arithmetic(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_eval(parse("(let ((x 10) (y 20)) (+ x y))"), {}, ctx))
        assert result == 30

    def test_if_when(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_eval(parse("(if true 1 2)"), {}, ctx))
        assert result == 1

    def test_map_lambda(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_eval(parse("(map (fn (x) (* x x)) (list 1 2 3))"), {}, ctx))
        assert result == [1, 4, 9]

    def test_macro_expansion(self):
        ctx = RequestContext()
        env = {}
        asyncio.get_event_loop().run_until_complete(
            async_eval(parse("(defmacro double (x) `(+ ,x ,x))"), env, ctx))
        result = asyncio.get_event_loop().run_until_complete(
            async_eval(parse("(double 5)"), env, ctx))
        assert result == 10


class TestAsyncRender:
    def test_simple_html(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_render(parse('(div :class "test" "hello")'), {}, ctx))
        assert result == '<div class="test">hello</div>'

    def test_component(self):
        ctx = RequestContext()
        env = {}
        evaluate(parse('(defcomp ~bold (&key text) (strong text))'), env)
        result = asyncio.get_event_loop().run_until_complete(
            async_render(parse('(~bold :text "hi")'), env, ctx))
        assert result == "<strong>hi</strong>"

    def test_let_with_render(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_render(parse('(let ((x "hello")) (span x))'), {}, ctx))
        assert result == "<span>hello</span>"

    def test_map_render(self):
        ctx = RequestContext()
        result = asyncio.get_event_loop().run_until_complete(
            async_render(parse('(ul (map (fn (x) (li x)) (list "a" "b")))'), {}, ctx))
        assert result == "<ul><li>a</li><li>b</li></ul>"

    def test_macro_in_render(self):
        ctx = RequestContext()
        env = {}
        evaluate(parse('(defmacro em-text (t) `(em ,t))'), env)
        result = asyncio.get_event_loop().run_until_complete(
            async_render(parse('(em-text "wow")'), env, ctx))
        assert result == "<em>wow</em>"
