from datetime import date
from decimal import Decimal

import pytest

from app import CollectionStatusEnum
from app.common.collections.types import DecimalAnswer, IntegerAnswer, TextSingleLineAnswer
from app.common.data.models import ComponentReference, get_ordered_nested_components
from app.common.data.types import (
    CollectionType,
    ConditionsOperator,
    DataSourceSchema,
    DataSourceSchemaColumn,
    DataSourceType,
    ExpressionType,
    NumberTypeEnum,
    QuestionDataOptions,
    QuestionDataType,
    QuestionPresentationOptions,
    RoleEnum,
)


class TestNestedComponents:
    def test_get_components_empty(self):
        assert get_ordered_nested_components([]) == []

    def test_get_components_flat(self, factories):
        form = factories.form.build()
        questions = factories.question.build_batch(3, form=form)
        assert get_ordered_nested_components(form.components) == questions

    def test_get_components_nested(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        group = factories.group.build(form=form)
        nested_questions = factories.question.build_batch(3, parent=group)
        g2 = factories.group.build(parent=group)
        nested_questions2 = factories.question.build_batch(3, parent=g2)
        q2 = factories.question.build(form=form)

        assert get_ordered_nested_components(form.components) == [
            q1,
            group,
            *nested_questions,
            g2,
            *nested_questions2,
            q2,
        ]

    def test_get_components_filters_nested(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        group = factories.group.build(form=form)
        nested_questions = factories.question.build_batch(3, parent=group)
        g2 = factories.group.build(parent=group)
        nested_questions2 = factories.question.build_batch(3, parent=g2)
        q2 = factories.question.build(form=form)

        assert form.cached_questions == [q1, *nested_questions, *nested_questions2, q2]
        assert group.cached_questions == [*nested_questions, *nested_questions2]

    def test_get_components_nested_orders(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form, order=2)
        group = factories.group.build(form=form, order=0)
        nested_q = factories.question.build(parent=group, order=0)
        q2 = factories.question.build(form=form, order=1)

        assert get_ordered_nested_components(form.components) == [group, nested_q, q2, q1]
        assert form.cached_questions == [nested_q, q2, q1]

    def test_get_components_nested_depth_5(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        group1 = factories.group.build(form=form)
        group2 = factories.group.build(parent=group1)
        group3 = factories.group.build(parent=group2)
        group4 = factories.group.build(parent=group3)
        group5 = factories.group.build(parent=group4)
        nested_q = factories.question.build(parent=group5)
        q2 = factories.question.build(form=form)

        assert get_ordered_nested_components(form.components) == [
            q1,
            group1,
            group2,
            group3,
            group4,
            group5,
            nested_q,
            q2,
        ]
        assert form.cached_questions == [q1, nested_q, q2]


class TestAddAnother:
    def test_add_another_false(self, factories):
        question = factories.question.build()
        assert question.add_another is False

    def test_add_another_true(self, factories):
        question = factories.question.build(add_another=True)
        assert question.add_another is True

    def test_no_add_another_container(self, factories):
        form = factories.form.build()
        question1 = factories.question.build(form=form)

        assert question1.add_another is False
        assert question1.add_another_container is None

        group1 = factories.group.build(form=form)
        question2 = factories.question.build(parent=group1)
        assert question2.add_another is False
        assert question2.add_another_container is None
        assert group1.add_another is False
        assert group1.add_another_container is None

        group2 = factories.group.build(parent=group1)
        question3 = factories.question.build(parent=group2)
        assert question3.add_another is False
        assert question3.add_another_container is None
        assert group2.add_another is False
        assert group2.add_another_container is None

    def test_add_another_container_is_self(self, factories):
        form = factories.form.build()
        question = factories.question.build(form=form, add_another=True)

        assert question.add_another_container == question

    def test_add_another_container_is_immediate_group_parent(self, factories):
        form = factories.form.build()
        group = factories.group.build(form=form, add_another=True)
        question = factories.question.build(parent=group)

        assert question.add_another is False
        assert group.add_another is True
        assert question.add_another_container == group
        assert group.add_another_container == group

    def test_add_another_container_is_ancestor_group(self, factories):
        form = factories.form.build()
        group1 = factories.group.build(form=form, add_another=True)
        group2 = factories.group.build(parent=group1)
        question = factories.question.build(parent=group2)

        assert question.add_another is False
        assert group1.add_another is True
        assert group2.add_another is False
        assert question.add_another_container == group1
        assert group2.add_another_container == group1
        assert group1.add_another_container == group1


class TestGrantAccessReports:
    def test_access_reports(self, factories):
        grant = factories.grant.build()
        report1 = factories.collection.build(grant=grant, status=CollectionStatusEnum.OPEN)
        report2 = factories.collection.build(grant=grant, status=CollectionStatusEnum.CLOSED)
        _ = factories.collection.build(grant=grant, status=CollectionStatusEnum.DRAFT)

        result = grant.access_reports
        assert len(result) == 2
        assert result[0].id == report1.id
        assert result[1].id == report2.id

    def test_get_access_reports_no_collections(self, db_session, factories):
        grant = factories.grant.build()

        results_grant_has_no_collections = grant.access_reports
        assert len(results_grant_has_no_collections) == 0

    def test_get_access_reports_wrong_state(self, factories):
        grant = factories.grant.build()
        factories.collection.build(grant=grant, status=CollectionStatusEnum.DRAFT)

        results_grant_has_collections_in_wrong_state = grant.access_reports
        assert len(results_grant_has_collections_in_wrong_state) == 0

    def test_get_access_reports_sort_order_status(self, factories):
        grant = factories.grant.build()
        report1 = factories.collection.build(grant=grant, status=CollectionStatusEnum.OPEN)
        report2 = factories.collection.build(grant=grant, status=CollectionStatusEnum.CLOSED)
        report3 = factories.collection.build(grant=grant, status=CollectionStatusEnum.OPEN)

        results = grant.access_reports
        assert len(results) == 3
        assert results[0].id == report1.id
        assert results[1].id == report3.id
        assert results[2].id == report2.id

    def test_get_access_reports_sort_order_date(self, factories):
        grant = factories.grant.build()
        report1 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2024, 1, 1)
        )
        report2 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2023, 1, 1)
        )
        report3 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2022, 1, 1)
        )

        results = grant.access_reports
        assert len(results) == 3
        assert results[0].id == report3.id
        assert results[1].id == report2.id
        assert results[2].id == report1.id

    def test_get_access_reports_sort_order_date_and_status(self, factories):
        grant = factories.grant.build()
        report0 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.CLOSED, submission_period_end_date=None
        )
        report1 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2024, 1, 1)
        )
        report2 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2023, 1, 1)
        )
        report3 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.OPEN, submission_period_end_date=date(2022, 1, 2)
        )
        report4 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.CLOSED, submission_period_end_date=date(2023, 2, 1)
        )
        report5 = factories.collection.build(
            grant=grant, status=CollectionStatusEnum.CLOSED, submission_period_end_date=date(2022, 1, 1)
        )

        results = grant.access_reports
        assert len(results) == 6
        assert results[0].id == report3.id
        assert results[1].id == report2.id
        assert results[2].id == report1.id
        assert results[3].id == report5.id
        assert results[4].id == report4.id
        assert results[5].id == report0.id


class TestGrantAccessPreAwardForms:
    def test_access_pre_award_forms(self, factories):
        grant = factories.grant.build()
        form1 = factories.collection.build(
            grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.OPEN
        )
        form2 = factories.collection.build(
            grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.CLOSED
        )
        _ = factories.collection.build(grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.DRAFT)
        _ = factories.collection.build(
            grant=grant, type=CollectionType.MONITORING_REPORT, status=CollectionStatusEnum.OPEN
        )
        _ = factories.collection.build(
            grant=grant, type=CollectionType.MONITORING_REPORT, status=CollectionStatusEnum.CLOSED
        )

        result = grant.access_pre_award_forms
        assert len(result) == 2
        assert result[0].id == form1.id
        assert result[1].id == form2.id

    def test_access_pre_award_forms_no_collections(self, db_session, factories):
        grant = factories.grant.build()

        result = grant.access_pre_award_forms
        assert len(result) == 0

    def test_get_access_pre_award_forms(self, factories):
        grant = factories.grant.build()
        form0 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.CLOSED,
            submission_period_end_date=None,
        )
        form1 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.OPEN,
            submission_period_end_date=date(2024, 1, 1),
        )
        form2 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.OPEN,
            submission_period_end_date=date(2023, 1, 1),
        )
        form3 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.OPEN,
            submission_period_end_date=date(2022, 1, 2),
        )
        form4 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.CLOSED,
            submission_period_end_date=date(2023, 2, 1),
        )
        form5 = factories.collection.build(
            grant=grant,
            type=CollectionType.APPLICATION,
            status=CollectionStatusEnum.CLOSED,
            submission_period_end_date=date(2022, 1, 1),
        )

        results = grant.access_pre_award_forms
        assert len(results) == 6
        assert results[0].id == form3.id
        assert results[1].id == form2.id
        assert results[2].id == form1.id
        assert results[3].id == form5.id
        assert results[4].id == form4.id
        assert results[5].id == form0.id

    def test_get_access_pre_award_forms_gets_all_forms_for_deliver_testing(self, factories, mocker):
        grant = factories.grant.build()
        user = factories.user.build()
        factories.user_role.build(
            user=user, permissions=[RoleEnum.MEMBER, RoleEnum.ADMIN], organisation=None, grant=None
        )

        form1 = factories.collection.build(
            grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.OPEN
        )
        form2 = factories.collection.build(
            grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.CLOSED
        )
        form3 = factories.collection.build(
            grant=grant, type=CollectionType.APPLICATION, status=CollectionStatusEnum.DRAFT
        )
        _ = factories.collection.build(
            grant=grant, type=CollectionType.MONITORING_REPORT, status=CollectionStatusEnum.OPEN
        )
        _ = factories.collection.build(
            grant=grant, type=CollectionType.MONITORING_REPORT, status=CollectionStatusEnum.CLOSED
        )
        _ = factories.collection.build(
            grant=grant, type=CollectionType.MONITORING_REPORT, status=CollectionStatusEnum.DRAFT
        )

        mocker.patch("flask.request.path", "/access/")
        result = grant.get_access_pre_award_forms_for_user(user=user)
        assert len(result) == 3
        assert result[0].id == form3.id
        assert result[1].id == form1.id
        assert result[2].id == form2.id


class TestFullConditionChain:
    def test_no_conditions(self, factories):
        q = factories.question.build()
        assert q.full_condition_chain == []

    def test_own_condition(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        cond = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        q2.owned_component_references = [ComponentReference(component=q2, expression=cond, depends_on_component=q1)]
        assert q2.full_condition_chain == [(ConditionsOperator.ALL, [cond])]

    def test_chain_collects_transitive_conditions(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        q3 = factories.question.build(form=form)
        cond_q2 = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        cond_q3 = factories.expression.build(question=q3, type_=ExpressionType.CONDITION, statement="True")
        q2.owned_component_references = [ComponentReference(component=q2, expression=cond_q2, depends_on_component=q1)]
        q3.owned_component_references = [ComponentReference(component=q3, expression=cond_q3, depends_on_component=q2)]
        assert q3.full_condition_chain == [(ConditionsOperator.ALL, [cond_q2]), (ConditionsOperator.ALL, [cond_q3])]

    def test_does_not_revisit_components(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        q3 = factories.question.build(form=form)
        cond_q2 = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        cond_q3 = factories.expression.build(question=q3, type_=ExpressionType.CONDITION, statement="True")
        q2.owned_component_references = [ComponentReference(component=q2, expression=cond_q2, depends_on_component=q1)]
        q3.owned_component_references = [
            ComponentReference(component=q3, expression=cond_q3, depends_on_component=q2),
            ComponentReference(component=q3, depends_on_component=q1),
        ]
        assert q3.full_condition_chain == [(ConditionsOperator.ALL, [cond_q2]), (ConditionsOperator.ALL, [cond_q3])]

    def test_skips_self_references(self, factories):
        q = factories.question.build()
        cond = factories.expression.build(question=q, type_=ExpressionType.CONDITION, statement="True")
        q.owned_component_references = [ComponentReference(component=q, expression=cond, depends_on_component=q)]
        assert q.full_condition_chain == [(ConditionsOperator.ALL, [cond])]

    def test_excludes_validation_expressions(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        cond = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        factories.expression.build(question=q2, type_=ExpressionType.VALIDATION, statement="len(value) > 0")
        q2.owned_component_references = [ComponentReference(component=q2, expression=cond, depends_on_component=q1)]
        assert q2.full_condition_chain == [(ConditionsOperator.ALL, [cond])]

    def test_parent_conditions(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        group = factories.group.build(form=q1.form)
        cond = factories.expression.build(question=group, type_=ExpressionType.CONDITION, statement="True")
        cr = ComponentReference(component=group, depends_on_component=q1)
        cond.component_references = [cr]
        group.owned_component_references = [cr]
        q2 = factories.question.build(parent=group)
        assert len(q2.get_full_condition_chain(ignore_parents=True)) == 0
        assert len(q2.get_full_condition_chain(ignore_parents=False)) == 1


class TestAllConditionalDependedOnComponents:
    def test_no_conditions(self, factories):
        q = factories.question.build()
        assert q.all_conditional_depended_on_components == set()

    def test_single_condition(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        cond = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        cr = ComponentReference(component=q2, depends_on_component=q1)
        cond.component_references = [cr]
        q2.owned_component_references = [cr]
        assert q2.all_conditional_depended_on_components == {q2}

    def test_chain_returns_all_conditional_components(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        q3 = factories.question.build(form=form)
        cond_q2 = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        cond_q3 = factories.expression.build(question=q3, type_=ExpressionType.CONDITION, statement="True")
        cr_q2 = ComponentReference(component=q2, depends_on_component=q1)
        cond_q2.component_references = [cr_q2]
        q2.owned_component_references = [cr_q2]
        cr_q3 = ComponentReference(component=q3, depends_on_component=q2)
        cond_q3.component_references = [cr_q3]
        q3.owned_component_references = [cr_q3]
        assert q3.all_conditional_depended_on_components == {q3, q2}

    def test_condition_without_component_references_excluded(self, factories):
        q = factories.question.build()
        factories.expression.build(question=q, type_=ExpressionType.CONDITION, statement="False")
        assert q.all_conditional_depended_on_components == set()


class TestIsSelfConditional:
    def test_not_conditional(self, factories):
        q = factories.question.build()
        assert q.is_self_conditional is False

    def test_is_conditional(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        q2 = factories.question.build(form=form)
        cond = factories.expression.build(question=q2, type_=ExpressionType.CONDITION, statement="True")
        cr = ComponentReference(component=q2, depends_on_component=q1)
        cond.component_references = [cr]
        q2.owned_component_references = [cr]
        assert q2.is_self_conditional is True

    def test_conditional_with_hardcoded_conditions(self, factories):
        q = factories.question.build()
        factories.expression.build(question=q, type_=ExpressionType.CONDITION, statement="False")
        assert q.is_self_conditional is True

    def test_ignores_conditions_on_parent(self, factories):
        form = factories.form.build()
        q1 = factories.question.build(form=form)
        group = factories.group.build(form=q1.form)
        cond = factories.expression.build(question=group, type_=ExpressionType.CONDITION, statement="True")
        cr = ComponentReference(component=group, depends_on_component=q1)
        cond.component_references = [cr]
        group.owned_component_references = [cr]
        q2 = factories.question.build(parent=group)
        assert group.is_self_conditional is True
        assert q2.is_self_conditional is False


def _text_col(original_column_name: str) -> DataSourceSchemaColumn:
    return DataSourceSchemaColumn(
        data_type=QuestionDataType.TEXT_SINGLE_LINE,
        presentation_options=QuestionPresentationOptions(),
        data_options=QuestionDataOptions(),
        original_column_name=original_column_name,
    )


def _decimal_col(
    original_column_name: str, prefix: str = "", suffix: str = "", max_decimal_places: int = 2
) -> DataSourceSchemaColumn:
    return DataSourceSchemaColumn(
        data_type=QuestionDataType.NUMBER,
        presentation_options=QuestionPresentationOptions(prefix=prefix or None, suffix=suffix or None),
        data_options=QuestionDataOptions(number_type=NumberTypeEnum.DECIMAL, max_decimal_places=max_decimal_places),
        original_column_name=original_column_name,
    )


def _integer_col(original_column_name: str, prefix: str = "", suffix: str = "") -> DataSourceSchemaColumn:
    return DataSourceSchemaColumn(
        data_type=QuestionDataType.NUMBER,
        presentation_options=QuestionPresentationOptions(prefix=prefix or None, suffix=suffix or None),
        data_options=QuestionDataOptions(number_type=NumberTypeEnum.INTEGER),
        original_column_name=original_column_name,
    )


class TestDataSourceMakePydanticModel:
    def test_returns_empty_dict_when_no_schema_and_2d_data(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
        )
        data_source.schema = None
        assert data_source.build_typed_org_item_data({"c_capital_allocation": "1000"}) == {}

    def test_text_column_returns_text_single_line_answer(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"theme-name": _text_col("Theme name")}),
        )

        result = data_source.build_typed_org_item_data({"theme-name": "Electricity"})

        assert isinstance(result["theme-name"], TextSingleLineAnswer)
        assert result["theme-name"].get_value_for_interpolation() == "Electricity"

    def test_decimal_column_with_prefix_formats_correctly(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"allocation": _decimal_col("Allocation", prefix="£")}),
        )

        result = data_source.build_typed_org_item_data({"allocation": "1000.00"})

        answer = result["allocation"]
        assert isinstance(answer, DecimalAnswer)
        assert answer.value == Decimal("1000.00")
        assert answer.get_value_for_interpolation() == "£1,000.00"

    def test_decimal_column_without_prefix_formats_correctly(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"allocation": _decimal_col("Allocation")}),
        )

        result = data_source.build_typed_org_item_data({"allocation": "500.50"})

        assert result["allocation"].get_value_for_interpolation() == "500.50"

    def test_integer_column_with_suffix_formats_correctly(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"headcount": _integer_col("Headcount", suffix=" people")}),
        )

        result = data_source.build_typed_org_item_data({"headcount": 42})

        answer = result["headcount"]
        assert isinstance(answer, IntegerAnswer)
        assert answer.value == 42
        assert answer.get_value_for_interpolation() == "42 people"

    def test_none_value_returns_none(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
        )

        result = data_source.build_typed_org_item_data({"c_allocation": None})
        assert result["c_allocation"] is None

    def test_missing_key_in_row_treated_as_none(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
        )

        result = data_source.build_typed_org_item_data({})
        assert result["c_allocation"] is None

    def test_multiple_columns_all_typed_correctly(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate(
                {
                    "theme-name": _text_col("Theme name"),
                    "allocation": _decimal_col("Allocation", prefix="£"),
                    "headcount": _integer_col("Headcount"),
                }
            ),
        )

        result = data_source.build_typed_org_item_data(
            {
                "theme-name": "Electricity",
                "allocation": "1000.00",
                "headcount": 5,
            }
        )

        assert isinstance(result["theme-name"], TextSingleLineAnswer)
        assert isinstance(result["allocation"], DecimalAnswer)
        assert isinstance(result["headcount"], IntegerAnswer)


class TestDataSourceBuildAnswerForColumn:
    def test_none_value_always_returns_none_regardless_of_column_type(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"allocation": _decimal_col("Allocation", prefix="£")}),
        )

        result = data_source._build_answer_for_column(None, data_source.schema.root["allocation"])
        assert result is None

    def test_decimal_value_stored_as_string_is_correctly_parsed(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"allocation": _decimal_col("Allocation", prefix="£")}),
        )

        result = data_source._build_answer_for_column("1234.56", data_source.schema.root["allocation"])

        assert isinstance(result, DecimalAnswer)
        assert result.value == Decimal("1234.56")

    def test_integer_value_stored_as_int_is_correctly_parsed(self, factories):
        data_source = factories.data_source.build(
            type=DataSourceType.GRANT_RECIPIENT,
        )

        result = data_source._build_answer_for_column(42, data_source.schema.root["c_allocation"])

        assert isinstance(result, IntegerAnswer)
        assert result.value == 42

    def test_prefix_and_suffix_set_when_present_on_column(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate(
                {
                    "allocation": _decimal_col("Allocation", prefix="£"),
                    "distance": _integer_col("Distance", suffix="km"),
                }
            ),
        )

        prefix_column = data_source._build_answer_for_column("1.5", data_source.schema.root["allocation"])
        assert isinstance(prefix_column, DecimalAnswer)
        assert prefix_column.prefix == "£"

        suffix_column = data_source._build_answer_for_column("1000", data_source.schema.root["distance"])
        assert isinstance(suffix_column, IntegerAnswer)
        assert suffix_column.suffix == "km"

    def test_no_prefix_or_suffix_when_not_set_on_column(self, factories):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"allocation": _decimal_col("Allocation")}),
        )

        result = data_source._build_answer_for_column("100.5", data_source.schema.root["allocation"])

        assert result.prefix is None
        assert result.suffix is None

    def test_unsupported_number_type_logs_error_and_raises(self, factories, caplog):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"column": _decimal_col("Bad column")}),
        )
        column = data_source.schema.root["column"]
        column.data_options.number_type = None

        with pytest.raises(ValueError, match="Unsupported number_type"):
            data_source._build_answer_for_column("123", column)

        assert len(caplog.messages) == 1
        assert "Unsupported number_type" in caplog.messages[0]
        assert "Bad column" in caplog.messages[0]

    def test_unsupported_data_type_logs_error_and_raises(self, factories, caplog):
        data_source = factories.data_source.build(
            name="Test data set",
            type=DataSourceType.GRANT_RECIPIENT,
            schema=DataSourceSchema.model_validate({"column": _text_col("Bad column")}),
        )
        column = data_source.schema.root["column"]
        column.data_type = QuestionDataType.DATE

        with pytest.raises(ValueError, match="Unsupported data_type"):
            data_source._build_answer_for_column("2024-01-01", column)

        assert len(caplog.messages) == 1
        assert "Unsupported data_type" in caplog.messages[0]
        assert "Bad column" in caplog.messages[0]
