"""
This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""

import uuid

import pytest

from control_backend.agents.bdi.agentspeak_ast import AstProgram
from control_backend.agents.bdi.agentspeak_generator import AgentSpeakGenerator
from control_backend.schemas.program import (
    BasicNorm,
    ConditionalNorm,
    Gesture,
    GestureAction,
    Goal,
    InferredBelief,
    KeywordBelief,
    LLMAction,
    LogicalOperator,
    Phase,
    Plan,
    Program,
    SemanticBelief,
    SpeechAction,
    Trigger,
)


@pytest.fixture
def generator():
    return AgentSpeakGenerator()


def test_generate_empty_program(generator):
    prog = Program(phases=[])
    code = generator.generate(prog)
    assert 'phase("end").' in code
    assert "!notify_cycle" in code


def test_generate_basic_norm(generator):
    norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="be nice")
    phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    assert f'norm("be nice") :- phase("{phase.id}").' in code


def test_generate_critical_norm(generator):
    norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="safety", critical=True)
    phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    assert f'critical_norm("safety") :- phase("{phase.id}").' in code


def test_generate_conditional_norm(generator):
    cond = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="please")
    norm = ConditionalNorm(id=uuid.uuid4(), name="n1", norm="help", condition=cond)
    phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    assert 'norm("help")' in code
    assert 'keyword_said("please")' in code
    assert f"force_norm_{generator._slugify_str(norm.norm)}" in code


def test_generate_goal_and_plan(generator):
    action = SpeechAction(id=uuid.uuid4(), name="s1", text="hello")
    plan = Plan(id=uuid.uuid4(), name="p1", steps=[action])
    # IMPORTANT: can_fail must be False for +achieved_ belief to be added
    goal = Goal(id=uuid.uuid4(), name="g1", description="desc", plan=plan, can_fail=False)
    phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    # Check trigger for goal
    goal_slug = generator._slugify_str(goal.name)
    assert f"+!{goal_slug}" in code
    assert f'phase("{phase.id}")' in code
    assert '!say("hello")' in code

    # Check success belief addition
    assert f"+achieved_{goal_slug}" in code


def test_generate_subgoal(generator):
    subplan = Plan(id=uuid.uuid4(), name="p2", steps=[])
    subgoal = Goal(id=uuid.uuid4(), name="sub1", description="sub", plan=subplan)

    plan = Plan(id=uuid.uuid4(), name="p1", steps=[subgoal])
    goal = Goal(id=uuid.uuid4(), name="g1", description="main", plan=plan)
    phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    subgoal_slug = generator._slugify_str(subgoal.name)
    # Main goal calls subgoal
    assert f"!{subgoal_slug}" in code
    # Subgoal plan exists
    assert f"+!{subgoal_slug}" in code


def test_generate_trigger(generator):
    cond = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc")
    plan = Plan(id=uuid.uuid4(), name="p1", steps=[])
    trigger = Trigger(id=uuid.uuid4(), name="t1", condition=cond, plan=plan)
    phase = Phase(id=uuid.uuid4(), norms=[], goals=[], triggers=[trigger])
    prog = Program(phases=[phase])

    code = generator.generate(prog)
    # Trigger logic is added to check_triggers
    assert f"{generator.slugify(cond)}" in code
    assert f'notify_trigger_start("{generator.slugify(trigger)}")' in code
    assert f'notify_trigger_end("{generator.slugify(trigger)}")' in code


def test_phase_transition(generator):
    phase1 = Phase(id=uuid.uuid4(), name="p1", norms=[], goals=[], triggers=[])
    phase2 = Phase(id=uuid.uuid4(), name="p2", norms=[], goals=[], triggers=[])
    prog = Program(phases=[phase1, phase2])

    code = generator.generate(prog)
    assert "transition_phase" in code
    assert f'phase("{phase1.id}")' in code
    assert f'phase("{phase2.id}")' in code
    assert "force_transition_phase" in code


def test_astify_gesture(generator):
    gesture = Gesture(type="single", name="wave")
    action = GestureAction(id=uuid.uuid4(), name="g1", gesture=gesture)
    ast = generator._astify(action)
    assert str(ast) == 'gesture("single", "wave")'


def test_astify_llm_action(generator):
    action = LLMAction(id=uuid.uuid4(), name="l1", goal="be funny")
    ast = generator._astify(action)
    assert str(ast) == 'reply_with_goal("be funny")'


def test_astify_inferred_belief_and(generator):
    left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a")
    right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b")
    inf = InferredBelief(
        id=uuid.uuid4(), name="i1", operator=LogicalOperator.AND, left=left, right=right
    )

    ast = generator._astify(inf)
    assert 'keyword_said("a") & keyword_said("b")' == str(ast)


def test_astify_inferred_belief_or(generator):
    left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a")
    right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b")
    inf = InferredBelief(
        id=uuid.uuid4(), name="i1", operator=LogicalOperator.OR, left=left, right=right
    )

    ast = generator._astify(inf)
    assert 'keyword_said("a") | keyword_said("b")' == str(ast)


def test_astify_semantic_belief(generator):
    sb = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc")
    ast = generator._astify(sb)
    assert str(ast) == f"semantic_{generator._slugify_str(sb.name)}"


def test_slugify_not_implemented(generator):
    with pytest.raises(NotImplementedError):
        generator.slugify("not a program element")


def test_astify_not_implemented(generator):
    with pytest.raises(NotImplementedError):
        generator._astify("not a program element")


def test_process_phase_transition_from_none(generator):
    # Initialize AstProgram manually as we are bypassing generate()
    generator._asp = AstProgram()
    # Should safely return doing nothing
    generator._add_phase_transition(None, None)

    assert len(generator._asp.plans) == 0
