"""Tests for rule-enforcement post-processor."""
from __future__ import annotations

import copy

from pipeline.rule_enforcer import enforce_rule_constraints


def _base_schema() -> dict:
    return {
        "schema_name": "test_sys",
        "menus": [
            {
                "menu_id": 1, "menu_name": "Ops", "sequence_number": 1,
                "description": "ops",
                "submenus": [
                    {
                        "submenu_id": 101, "submenu_name": "Orders",
                        "sequence_number": 1, "description": "orders",
                        "tables": [
                            {
                                "table_name": "orders",
                                "comment": "Order header",
                                "columns": [
                                    {"name": "id", "type": "SERIAL",
                                     "constraints": "PRIMARY KEY"},
                                ],
                                "foreign_keys": [],
                            }
                        ],
                    }
                ],
            }
        ],
        "assumptions": [],
    }


def _base_cir(applies_to="Order") -> dict:
    return {
        "rules": [
            {
                "name": "min_one_line_item",
                "description": "An order must have at least one line item.",
                "applies_to": applies_to,
                "rule_type": "required",
            }
        ]
    }


def test_appends_rule_to_matching_table_comment():
    s = _base_schema()
    cir = _base_cir(applies_to="orders")
    enforce_rule_constraints(s, cir)
    tbl = s["menus"][0]["submenus"][0]["tables"][0]
    assert "min_one_line_item" in tbl["comment"]
    stats = s["_rule_enforcer"]
    assert stats["appended_to_table_comment"] == 1


def test_fuzzy_matches_camelcase_to_snake():
    s = _base_schema()
    cir = _base_cir(applies_to="Order")  # CamelCase singular
    enforce_rule_constraints(s, cir)
    tbl = s["menus"][0]["submenus"][0]["tables"][0]
    assert "min_one_line_item" in tbl["comment"]


def test_falls_back_to_assumptions_when_no_table_match():
    s = _base_schema()
    cir = _base_cir(applies_to="NonexistentEntity")
    enforce_rule_constraints(s, cir)
    stats = s["_rule_enforcer"]
    assert stats["added_to_assumptions"] == 1
    assert any("min_one_line_item" in a for a in s["assumptions"])


def test_idempotent_on_second_pass():
    s = _base_schema()
    cir = _base_cir(applies_to="orders")
    enforce_rule_constraints(s, cir)
    snap = copy.deepcopy(s)
    enforce_rule_constraints(s, cir)
    # Comment unchanged on second pass
    assert s["menus"][0]["submenus"][0]["tables"][0]["comment"] == \
           snap["menus"][0]["submenus"][0]["tables"][0]["comment"]
    # Stats reflect "already present"
    assert s["_rule_enforcer"]["already_present"] == 1
    assert s["_rule_enforcer"]["appended_to_table_comment"] == 0


def test_no_rules_no_op():
    s = _base_schema()
    cir = {"rules": []}
    enforce_rule_constraints(s, cir)
    stats = s["_rule_enforcer"]
    assert stats["rules_total"] == 0
    assert stats["appended_to_table_comment"] == 0


def test_skips_cfg_tables():
    """cfg_* tables shouldn't be rule targets even with name match."""
    s = _base_schema()
    s["menus"][0]["submenus"][0]["tables"].append({
        "table_name": "cfg_orders_status",
        "comment": "cfg lookup",
        "columns": [],
        "foreign_keys": [],
    })
    cir = _base_cir(applies_to="orders")
    enforce_rule_constraints(s, cir)
    cfg_tbl = next(t for t in s["menus"][0]["submenus"][0]["tables"]
                    if t["table_name"] == "cfg_orders_status")
    assert "min_one_line_item" not in (cfg_tbl["comment"] or "")


def test_handles_multiple_rules():
    s = _base_schema()
    cir = {
        "rules": [
            {"name": "rule_a", "description": "Rule A.",
             "applies_to": "orders", "rule_type": "required"},
            {"name": "rule_b", "description": "Rule B.",
             "applies_to": "orders", "rule_type": "other"},
            {"name": "rule_c", "description": "Rule C.",
             "applies_to": "Nope", "rule_type": "other"},
        ]
    }
    enforce_rule_constraints(s, cir)
    stats = s["_rule_enforcer"]
    assert stats["appended_to_table_comment"] == 2
    assert stats["added_to_assumptions"] == 1
    assert stats["rules_total"] == 3


def test_preserves_existing_table_comment():
    s = _base_schema()
    s["menus"][0]["submenus"][0]["tables"][0]["comment"] = "Existing description."
    cir = _base_cir(applies_to="orders")
    enforce_rule_constraints(s, cir)
    tbl = s["menus"][0]["submenus"][0]["tables"][0]
    assert "Existing description" in tbl["comment"]
    assert "min_one_line_item" in tbl["comment"]


def test_rule_with_empty_description():
    s = _base_schema()
    cir = {
        "rules": [{
            "name": "rule_x",
            "description": "",
            "applies_to": "orders",
            "rule_type": "required",
        }]
    }
    enforce_rule_constraints(s, cir)
    tbl = s["menus"][0]["submenus"][0]["tables"][0]
    assert "rule_x" in tbl["comment"]
