"""
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 pydantic import ValidationError

from control_backend.schemas.program import (
    BasicNorm,
    ConditionalNorm,
    Goal,
    InferredBelief,
    KeywordBelief,
    LogicalOperator,
    Phase,
    Plan,
    Program,
    SemanticBelief,
    Trigger,
)


def base_norm() -> BasicNorm:
    return BasicNorm(
        id=uuid.uuid4(),
        name="testNormName",
        norm="testNormNorm",
        critical=False,
    )


def base_goal() -> Goal:
    return Goal(
        id=uuid.uuid4(),
        name="testGoalName",
        description="This description can be used to determine whether the goal has been achieved.",
        plan=Plan(
            id=uuid.uuid4(),
            name="testGoalPlanName",
            steps=[],
        ),
        can_fail=False,
    )


def base_trigger() -> Trigger:
    return Trigger(
        id=uuid.uuid4(),
        name="testTriggerName",
        condition=KeywordBelief(
            id=uuid.uuid4(),
            name="testTriggerKeywordBeliefTriggerName",
            keyword="Keyword",
        ),
        plan=Plan(
            id=uuid.uuid4(),
            name="testTriggerPlanName",
            steps=[],
        ),
    )


def base_phase() -> Phase:
    return Phase(
        id=uuid.uuid4(),
        norms=[base_norm()],
        goals=[base_goal()],
        triggers=[base_trigger()],
    )


def base_program() -> Program:
    return Program(phases=[base_phase()])


def invalid_program() -> dict:
    # wrong types inside phases list (not Phase objects)
    return {
        "phases": [
            {"id": uuid.uuid4()},  # incomplete
            {"not_a_phase": True},
        ]
    }


def test_valid_program():
    program = base_program()
    validated = Program.model_validate(program)
    assert isinstance(validated, Program)
    assert validated.phases[0].norms[0].norm == "testNormNorm"


def test_valid_deepprogram():
    program = base_program()
    validated = Program.model_validate(program)
    # validate nested components directly
    phase = validated.phases[0]
    assert isinstance(phase.goals[0], Goal)
    assert isinstance(phase.triggers[0], Trigger)
    assert isinstance(phase.norms[0], BasicNorm)


def test_invalid_program():
    bad = invalid_program()
    with pytest.raises(ValidationError):
        Program.model_validate(bad)


def test_conditional_norm_parsing():
    """
    Check that pydantic is able to preserve the type of the norm, that it doesn't lose its
    "condition" field when serializing and deserializing.
    """
    norm = ConditionalNorm(
        name="testNormName",
        id=uuid.uuid4(),
        norm="testNormNorm",
        critical=False,
        condition=KeywordBelief(
            name="testKeywordBelief",
            id=uuid.uuid4(),
            keyword="testKeywordBelief",
        ),
    )
    program = Program(
        phases=[
            Phase(
                name="Some phase",
                id=uuid.uuid4(),
                norms=[norm],
                goals=[],
                triggers=[],
            ),
        ],
    )

    parsed_program = Program.model_validate_json(program.model_dump_json())
    parsed_norm = parsed_program.phases[0].norms[0]

    assert hasattr(parsed_norm, "condition")
    assert isinstance(parsed_norm, ConditionalNorm)


def test_belief_type_parsing():
    """
    Check that pydantic is able to discern between the different types of beliefs when serializing
    and deserializing.
    """
    keyword_belief = KeywordBelief(
        name="testKeywordBelief",
        id=uuid.uuid4(),
        keyword="something",
    )
    semantic_belief = SemanticBelief(
        name="testSemanticBelief",
        id=uuid.uuid4(),
        description="something",
    )
    inferred_belief = InferredBelief(
        name="testInferredBelief",
        id=uuid.uuid4(),
        operator=LogicalOperator.OR,
        left=keyword_belief,
        right=semantic_belief,
    )

    program = Program(
        phases=[
            Phase(
                name="Some phase",
                id=uuid.uuid4(),
                norms=[],
                goals=[],
                triggers=[
                    Trigger(
                        name="testTriggerKeywordTrigger",
                        id=uuid.uuid4(),
                        condition=keyword_belief,
                        plan=Plan(name="testTriggerPlanName", id=uuid.uuid4(), steps=[]),
                    ),
                    Trigger(
                        name="testTriggerSemanticTrigger",
                        id=uuid.uuid4(),
                        condition=semantic_belief,
                        plan=Plan(name="testTriggerPlanName", id=uuid.uuid4(), steps=[]),
                    ),
                    Trigger(
                        name="testTriggerInferredTrigger",
                        id=uuid.uuid4(),
                        condition=inferred_belief,
                        plan=Plan(name="testTriggerPlanName", id=uuid.uuid4(), steps=[]),
                    ),
                ],
            ),
        ],
    )

    parsed_program = Program.model_validate_json(program.model_dump_json())

    parsed_keyword_belief = parsed_program.phases[0].triggers[0].condition
    assert isinstance(parsed_keyword_belief, KeywordBelief)

    parsed_semantic_belief = parsed_program.phases[0].triggers[1].condition
    assert isinstance(parsed_semantic_belief, SemanticBelief)

    parsed_inferred_belief = parsed_program.phases[0].triggers[2].condition
    assert isinstance(parsed_inferred_belief, InferredBelief)
