#  Copyright 2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""Validation failures."""

import logging
from pathlib import Path

from mafw.mafw_errors import (
    DuplicateReplicaIssue,
    InvalidFilterConditionIssue,
    InvalidFilterLogicIssue,
    MissingProcessorsToRunIssue,
    ProcessorsToRunNotListIssue,
    UnknownGroupMemberIssue,
    UnknownProcessorsToRunEntryIssue,
)
from mafw.steering.builder import SteeringBuilder, ValidationLevel
from mafw.steering.models import (
    Condition,
    ConditionalFilterConfig,
    FieldFilterConfig,
    ModelFilterConfig,
    ProcessorConfig,
)
from mafw.steering.validation import validate


class DuplicateKeysDict(dict):
    """Dict shim that can pretend to have duplicate keys for testing."""

    def __init__(self, data: dict[str, ProcessorConfig], keys: list[str]):
        super().__init__(data)
        self._keys = keys

    def keys(self) -> list[str]:
        return self._keys


def test_validation_requires_processor() -> None:
    builder = SteeringBuilder()
    issues = validate(builder)
    assert any(isinstance(issue, MissingProcessorsToRunIssue) for issue in issues)


def test_validation_unknown_processor_entry() -> None:
    builder = SteeringBuilder()
    builder.globals.processors_to_run = ['Missing']
    issues = validate(builder)
    assert any(isinstance(issue, UnknownProcessorsToRunEntryIssue) for issue in issues)


def test_validation_group_member_unknown() -> None:
    builder = SteeringBuilder()
    builder.add_processor('P')
    builder.add_group('A', ['Missing'])
    builder.globals.processors_to_run = ['A']
    issues = validate(builder)
    assert any(isinstance(issue, UnknownGroupMemberIssue) for issue in issues)


def test_validation_accepts_valid_configuration() -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.add_replica('Proc', '1')
    builder.globals.processors_to_run = ['Proc', 'Proc#1']
    issues = validate(builder)
    assert issues == []


def test_full_validation_logs_warning(caplog) -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    caplog.set_level(logging.WARNING)
    issues = validate(builder, ValidationLevel.FULL)
    assert issues == []
    assert 'FULL validation level is not implemented yet' in caplog.text


def test_processors_to_run_not_list_issue(tmp_path: Path) -> None:
    content = """
processors_to_run = "Proc"

[UserInterface]
interface = "rich"

[Proc]
param = 1
"""
    path = tmp_path / 'invalid.toml'
    path.write_text(content)
    builder = SteeringBuilder.from_toml(path)
    issues = validate(builder)
    assert any(isinstance(issue, ProcessorsToRunNotListIssue) for issue in issues)


def test_duplicate_replica_issue() -> None:
    builder = SteeringBuilder()
    builder.globals.processors_to_run = ['Proc#1']
    builder.processors = DuplicateKeysDict({'Proc#1': ProcessorConfig(name='Proc#1')}, ['Proc#1', 'Proc#1'])
    issues = validate(builder)
    assert any(isinstance(issue, DuplicateReplicaIssue) for issue in issues)


def test_validation_detects_invalid_condition() -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    model_config = ModelFilterConfig(name='ModelA', model='ModelA')
    model_config.conditions['cond'] = Condition(operator='==', value=None, is_valid=False)
    processor = builder.get_processor_config('Proc')
    processor.filters.setdefault('ModelA', []).append(model_config)

    issues = validate(builder)
    assert any(isinstance(issue, InvalidFilterConditionIssue) for issue in issues)


def test_validation_detects_invalid_logic() -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    builder.set_filter_logic('Proc', 'AND')

    issues = validate(builder)
    assert any(isinstance(issue, InvalidFilterLogicIssue) for issue in issues)


def test_validation_reports_logic_issue_for_model_and_field() -> None:
    """Test _report_logic_issue for ModelFilterConfig and FieldFilterConfig."""
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    proc = builder.get_processor_config('Proc')

    # Model logic issue
    mfc = ModelFilterConfig(name='ModelA', model='ModelA')
    mfc.logic_str_original = 'INVALID'
    mfc.logic_is_valid = False
    proc.filters['ModelA'] = [mfc]

    # Field logic issue
    ffc = FieldFilterConfig(name='FieldA', model='ModelA', field_name='FieldA')
    ffc.logic_str_original = 'ALSO INVALID'
    ffc.logic_is_valid = False
    proc.filters['ModelA'].append(ffc)

    issues = validate(builder)
    logic_issues = [i for i in issues if isinstance(i, InvalidFilterLogicIssue)]
    assert len(logic_issues) == 2
    messages = [str(i) for i in logic_issues]
    assert any("model 'ModelA'" in m for m in messages)
    assert any("field 'ModelA.FieldA'" in m for m in messages)


def test_validation_reports_field_filter_condition_issue() -> None:
    """Test _report_condition for FieldFilterConfig."""
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    proc = builder.get_processor_config('Proc')

    ffc = FieldFilterConfig(name='FieldA', model='ModelA', field_name='FieldA')
    ffc.conditions['cond'] = Condition(operator='==', value=None, is_valid=False)
    proc.filters['ModelA'] = [ffc]

    issues = validate(builder)
    assert any(isinstance(issue, InvalidFilterConditionIssue) and "field 'FieldA'" in str(issue) for issue in issues)


def test_validation_reports_conditional_issues() -> None:
    """Test _report_conditional for various failure modes."""
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.globals.processors_to_run = ['Proc']
    proc = builder.get_processor_config('Proc')

    # Invalid IF
    cf1 = ConditionalFilterConfig(name='cf1', model='ModelA', condition_field='Field1')
    cf1.condition = Condition(operator='==', value=1, is_valid=False)

    # Invalid THEN
    cf2 = ConditionalFilterConfig(name='cf2', model='ModelA', then_field='Field2')
    cf2.condition = Condition(operator='==', value=1, is_valid=True)
    cf2.then_clause = Condition(operator='==', value=2, is_valid=False)

    # Invalid ELSE
    cf3 = ConditionalFilterConfig(name='cf3', model='ModelA', else_field='Field3')
    cf3.condition = Condition(operator='==', value=1, is_valid=True)
    cf3.then_clause = Condition(operator='==', value=2, is_valid=True)
    cf3.else_clause = Condition(operator='==', value=3, is_valid=False)

    proc.filters['ModelA'] = [cf1, cf2, cf3]

    issues = validate(builder)
    cond_issues = [i for i in issues if isinstance(i, InvalidFilterConditionIssue)]
    messages = [str(i) for i in cond_issues]

    assert any('cf1:if' in m and "field 'Field1'" in m for m in messages)
    assert any('cf2:then' in m and "field 'Field2'" in m for m in messages)
    assert any('cf3:else' in m and "field 'Field3'" in m for m in messages)


def test_logic_text_empty_string() -> None:
    """Test _logic_text with empty/whitespace string."""
    from mafw.steering.validation import _logic_text

    builder = SteeringBuilder()
    builder.add_processor('Proc')
    proc = builder.get_processor_config('Proc')
    proc.logic_str_original = '   '
    assert _logic_text(proc) is None

    proc.logic_str_original = ''
    assert _logic_text(proc) is None


def test_validate_missing_ui_section(tmp_path) -> None:
    """Test _validate_global_sections when UserInterface is missing."""
    content = """
processors_to_run = ["Proc"]

[Proc]
param = 1
"""
    path = tmp_path / 'no_ui.toml'
    path.write_text(content)
    builder = SteeringBuilder.from_toml(path)
    issues = validate(builder)
    from mafw.mafw_errors import MissingUserInterfaceSectionIssue

    assert any(isinstance(issue, MissingUserInterfaceSectionIssue) for issue in issues)
