#  Copyright 2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""Tests for logic AST as SSOT."""

from pathlib import Path

from mafw.db.db_filter import ast_to_string
from mafw.steering.builder import SteeringBuilder
from mafw.steering.models import FieldFilterConfig, ModelFilterConfig


def test_logic_loading_into_ast(tmp_path: Path) -> None:
    steering_content = """
[Proc.__filter__.Model]
__logic__ = "A AND (B OR NOT C)"
A = 1
B = 2
C = 3
"""
    steering_file = tmp_path / 'test_logic.toml'
    steering_file.write_text(steering_content)

    builder = SteeringBuilder.from_toml(steering_file)
    proc_config = builder.get_processor_config('Proc')
    model_filters = proc_config.filters['Model']
    model_config = next(f for f in model_filters if isinstance(f, ModelFilterConfig))

    assert model_config.logic_str_original == 'A AND (B OR NOT C)'
    assert model_config.logic_ast is not None
    assert model_config.logic_dirty is False

    # Verify AST structure
    ast = model_config.logic_ast
    assert ast[0] == 'AND'
    assert ast[1] == ('NAME', 'A')
    assert ast[2][0] == 'OR'


def test_logic_modification_triggers_dirty(tmp_path: Path) -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')
    builder.set_filter_field('Proc', 'Model', 'A', 1)
    builder.set_filter_field('Proc', 'Model', 'B', 2)

    # Initially no logic
    proc_config = builder.get_processor_config('Proc')
    model_config = next(f for f in proc_config.filters['Model'] if isinstance(f, ModelFilterConfig))
    assert model_config.logic_ast is None

    # Set logic via string (simulating GUI or tool)
    model_config.logic_ast = ('OR', ('NAME', 'A'), ('NAME', 'B'))
    model_config.logic_dirty = True

    # Serialize and check
    doc = builder.to_document()
    assert doc['Proc']['__filter__']['Model']['__logic__'] == 'A OR B'


def test_ast_to_string_precedence() -> None:
    # A AND (B OR C)
    ast = ('AND', ('NAME', 'A'), ('OR', ('NAME', 'B'), ('NAME', 'C')))
    assert ast_to_string(ast) == 'A AND (B OR C)'

    # (A OR B) AND C
    ast = ('AND', ('OR', ('NAME', 'A'), ('NAME', 'B')), ('NAME', 'C'))
    assert ast_to_string(ast) == '(A OR B) AND C'

    # NOT (A AND B)
    ast = ('NOT', ('AND', ('NAME', 'A'), ('NAME', 'B')))
    assert ast_to_string(ast) == 'NOT (A AND B)'

    # NOT A AND B  -> (NOT A) AND B
    ast = ('AND', ('NOT', ('NAME', 'A')), ('NAME', 'B'))
    assert ast_to_string(ast) == 'NOT A AND B'


def test_roundtrip_preserves_formatting(tmp_path: Path) -> None:
    # Original has extra spaces
    original_logic = 'A    AND   ( B OR NOT C )'
    steering_content = f"""
[Proc.__filter__.Model]
__logic__ = "{original_logic}"
A = 1
B = 2
C = 3
"""
    steering_file = tmp_path / 'test_roundtrip.toml'
    steering_file.write_text(steering_content)

    builder = SteeringBuilder.from_toml(steering_file)
    output_file = tmp_path / 'output.toml'
    builder.write(output_file)

    # Read back and verify original string is used because it's not dirty
    output_content = output_file.read_text()
    assert f'__logic__ = "{original_logic}"' in output_content


def test_processor_level_logic(tmp_path: Path) -> None:
    steering_content = """
[Proc.__filter__]
__logic__ = "Model1 AND Model2"

[Proc.__filter__.Model1]
f1 = 1

[Proc.__filter__.Model2]
f2 = 2
"""
    steering_file = tmp_path / 'test_proc_logic.toml'
    steering_file.write_text(steering_content)

    builder = SteeringBuilder.from_toml(steering_file)
    proc_config = builder.get_processor_config('Proc')

    assert proc_config.logic_str_original == 'Model1 AND Model2'
    assert proc_config.logic_ast == ('AND', ('NAME', 'Model1'), ('NAME', 'Model2'))
    assert proc_config.logic_dirty is False

    # Modify
    proc_config.logic_ast = ('OR', ('NAME', 'Model1'), ('NAME', 'Model2'))
    proc_config.logic_dirty = True

    doc = builder.to_document()
    assert doc['Proc']['__filter__']['__logic__'] == 'Model1 OR Model2'


def test_field_level_logic(tmp_path: Path) -> None:
    steering_content = """
[Proc.__filter__.Model.field]
__logic__ = "a OR b"
a = { op = ">", value = 10 }
b = { op = "<", value = 5 }
"""
    steering_file = tmp_path / 'test_field_logic.toml'
    steering_file.write_text(steering_content)

    builder = SteeringBuilder.from_toml(steering_file)
    proc_config = builder.get_processor_config('Proc')
    model_filters = proc_config.filters['Model']
    field_config = next(f for f in model_filters if isinstance(f, FieldFilterConfig))

    assert field_config.logic_str_original == 'a OR b'
    assert field_config.logic_ast == ('OR', ('NAME', 'a'), ('NAME', 'b'))
    assert field_config.logic_dirty is False

    # Modify
    field_config.logic_ast = ('AND', ('NAME', 'a'), ('NAME', 'b'))
    field_config.logic_dirty = True

    doc = builder.to_document()
    assert doc['Proc']['__filter__']['Model']['field']['__logic__'] == 'a AND b'


def test_set_filter_logic_method() -> None:
    builder = SteeringBuilder()
    builder.add_processor('Proc')

    builder.set_filter_logic('Proc', 'A AND B')
    proc_config = builder.get_processor_config('Proc')

    assert proc_config.logic_str_original == 'A AND B'
    assert proc_config.logic_ast == ('AND', ('NAME', 'A'), ('NAME', 'B'))
    assert proc_config.logic_dirty is True

    doc = builder.to_document()
    assert doc['Proc']['__logic__'] == 'A AND B'
