"""Tests for Step 5 coverage validators (Layer 3)."""

from pipeline.coverage import (
    compute_coverage,
    entity_coverage,
    mantara_validation_pass,
    rule_constraint_coverage,
    schema_completeness,
    state_seed_coverage,
    visual_fidelity,
)


# --------------------------------------------------------------------------- #
# Fixtures
# --------------------------------------------------------------------------- #
def _cir() -> dict:
    return {
        "manifest": {"name": "Test", "domain": "warehouse"},
        "entities": [
            {"name": "Order",
             "fields": [
                 {"name": "id", "type": "uuid", "primary_key": True},
                 {"name": "orderNumber", "type": "string", "required": True},
                 {"name": "status", "type": "enum"},
             ]},
            {"name": "Item",
             "fields": [
                 {"name": "id", "type": "uuid", "primary_key": True},
                 {"name": "skuNumber", "type": "string"},
             ]},
        ],
        "workflows": [{
            "name": "order_lifecycle",
            "bound_entity": "Order",
            "states": ["draft", "submitted", "fulfilled"],
        }],
        "rules": [
            {"name": "min_one_item", "applies_to": "Order",
             "description": "Order must have ≥1 item"},
        ],
    }


def _mantara_schema_full() -> dict:
    """Full coverage — every CIR thing has a Mantara counterpart."""
    return {
        "menus": [{
            "submenus": [
                {
                    "tables": [{
                        "table_name": "order",
                        "columns": [
                            {"name": "id"}, {"name": "order_number"},
                            {"name": "status"}, {"name": "submenu_id"},
                        ],
                        "foreign_keys": [],
                    }],
                },
                {
                    "tables": [{
                        "table_name": "item",
                        "columns": [
                            {"name": "id"}, {"name": "sku_number"},
                            {"name": "submenu_id"},
                        ],
                    }],
                },
                {
                    "tables": [{
                        "table_name": "cfg_order_status",
                        "columns": [{"name": "code"}, {"name": "description"}],
                        "values": [
                            {"code": "draft"}, {"code": "submitted"}, {"code": "fulfilled"},
                        ],
                    }],
                },
            ],
        }],
    }


def _mantara_schema_partial() -> dict:
    """Partial — Item entity is missing from Mantara's output."""
    return {
        "menus": [{
            "submenus": [{
                "tables": [{
                    "table_name": "order",
                    "columns": [{"name": "id"}, {"name": "order_number"}],
                }],
            }],
        }],
    }


# --------------------------------------------------------------------------- #
# 1. schema_completeness
# --------------------------------------------------------------------------- #
def test_schema_completeness_full() -> None:
    score, det = schema_completeness(_cir(), _mantara_schema_full())
    assert score == 100.0
    assert det["missing"] == []


def test_schema_completeness_partial() -> None:
    score, det = schema_completeness(_cir(), _mantara_schema_partial())
    assert score == 50.0  # 1 of 2 entities present
    assert "Item" in det["missing"]


def test_schema_completeness_empty_cir() -> None:
    score, det = schema_completeness({"entities": []}, _mantara_schema_full())
    assert score == 100.0  # vacuously


# --------------------------------------------------------------------------- #
# 2. entity_coverage
# --------------------------------------------------------------------------- #
def test_entity_coverage_full() -> None:
    score, det = entity_coverage(_cir(), _mantara_schema_full())
    # 3 fields total in CIR (orderNumber, status, skuNumber — id auto-skipped)
    assert score == 100.0
    assert det["expected"] == 3


def test_entity_coverage_partial() -> None:
    """If Mantara's table has fewer columns than CIR, score drops."""
    cir = _cir()
    schema = {
        "menus": [{
            "submenus": [{
                "tables": [{
                    "table_name": "order",
                    "columns": [{"name": "id"}],  # missing orderNumber, status
                }, {
                    "table_name": "item",
                    "columns": [{"name": "id"}, {"name": "sku_number"}],
                }],
            }],
        }],
    }
    score, det = entity_coverage(cir, schema)
    # 1 of 3 covered (sku_number), 2 missing
    assert score < 100.0
    assert det["covered"] == 1


# --------------------------------------------------------------------------- #
# 3. state_seed_coverage
# --------------------------------------------------------------------------- #
def test_state_seed_coverage_full() -> None:
    score, det = state_seed_coverage(_cir(), _mantara_schema_full())
    assert score == 100.0
    assert det["expected"] == 3


def test_state_seed_coverage_no_workflows() -> None:
    cir = {"workflows": []}
    score, _ = state_seed_coverage(cir, _mantara_schema_full())
    assert score == 100.0


def test_state_seed_coverage_missing_cfg_table() -> None:
    cir = _cir()
    schema = {"menus": [{"submenus": [{"tables": [{"table_name": "order", "columns": []}]}]}]}
    score, det = state_seed_coverage(cir, schema)
    assert score == 0.0
    assert det["expected"] == 3


# --------------------------------------------------------------------------- #
# 4. rule_constraint_coverage
# --------------------------------------------------------------------------- #
def test_rule_constraint_coverage_via_sql() -> None:
    cir = _cir()
    sql = "CREATE TABLE order (...) -- min_one_item: order must have ≥1 item"
    score, det = rule_constraint_coverage(cir, sql)
    assert score == 100.0


def test_rule_constraint_coverage_no_match() -> None:
    cir = _cir()
    sql = "CREATE TABLE order ();"
    score, det = rule_constraint_coverage(cir, sql)
    assert score == 0.0
    assert "min_one_item" in det["missing"]


def test_rule_constraint_coverage_no_rules() -> None:
    cir = {"rules": []}
    score, _ = rule_constraint_coverage(cir, "")
    assert score == 100.0


# --------------------------------------------------------------------------- #
# 5. visual_fidelity (Mermaid ER vs Mantara FKs)
# --------------------------------------------------------------------------- #
def test_visual_fidelity_match() -> None:
    er_mmd = """erDiagram
  Order ||--o{ Item : "has many"
"""
    schema = {
        "menus": [{
            "submenus": [{
                "tables": [{
                    "table_name": "item",
                    "foreign_keys": [{"column": "order_id",
                                      "references": "schema.order(id)"}],
                }],
            }],
        }],
    }
    score, det = visual_fidelity(er_mmd, schema)
    # FK is item→order, Mermaid is Order→Item — fuzzy match should accept
    assert score == 100.0


def test_visual_fidelity_missing_fk() -> None:
    er_mmd = "erDiagram\n  Order ||--o{ Item : has\n"
    schema = {"menus": [{"submenus": [{"tables": [{"table_name": "order"},
                                                   {"table_name": "vendor"}]}]}]}
    score, det = visual_fidelity(er_mmd, schema)
    assert score == 0.0
    assert det["missing"]


def test_visual_fidelity_no_mermaid() -> None:
    score, _ = visual_fidelity("", {"menus": []})
    assert score == 100.0  # vacuous


# --------------------------------------------------------------------------- #
# 6. mantara_validation_pass
# --------------------------------------------------------------------------- #
def test_mantara_validation_pass_clean() -> None:
    score, det = mantara_validation_pass({"is_valid": True, "errors": []})
    assert score == 100.0


def test_mantara_validation_pass_with_errors() -> None:
    # 5 errors → 100 - 5*4 = 80
    score, det = mantara_validation_pass({"is_valid": False,
                                            "errors": ["e1", "e2", "e3", "e4", "e5"]})
    assert score == 80.0
    assert det["errors"] == 5


def test_mantara_validation_pass_30_errors() -> None:
    """30 errors → max(0, 100 - 120) = 0."""
    score, _ = mantara_validation_pass({"is_valid": False,
                                          "errors": ["e"] * 30})
    assert score == 0.0


def test_mantara_validation_pass_none() -> None:
    score, det = mantara_validation_pass(None)
    assert score == 0.0
    assert det["is_valid"] is False


# --------------------------------------------------------------------------- #
# Aggregate
# --------------------------------------------------------------------------- #
def test_compute_coverage_full_pass() -> None:
    # Provide rich mantara_sql so benchmark_format_compliance scores high
    rich_sql = (
        "-- min_one_item\n"
        "COMMENT ON TABLE x IS 'a';\n" * 5
        + "CREATE INDEX idx1 ON x(y);\n" * 10
        + "CHECK (a > 0)\n" * 5
    )
    # Add audit columns to the fixture in-place so audit_score = 100
    schema = _mantara_schema_full()
    for menu in schema["menus"]:
        for sub in menu.get("submenus") or []:
            for tbl in sub.get("tables") or []:
                tbl.setdefault("columns", []).extend([
                    {"name": "created_at", "type": "TIMESTAMP"},
                    {"name": "updated_at", "type": "TIMESTAMP"},
                    {"name": "is_active", "type": "BOOLEAN"},
                ])
    rep = compute_coverage(
        _cir(),
        mantara_schema=schema,
        mantara_sql=rich_sql,
        mantara_validation={"is_valid": True, "errors": []},
        er_mmd="",
    )
    assert rep.overall_score >= 90.0
    # All non-format sub-scores should hit 100; benchmark_format_compliance
    # may be slightly below due to no FK-to-cfg in the toy fixture.
    for k, v in rep.sub_scores.items():
        if k == "benchmark_format_compliance":
            continue
        assert v == 100.0, f"{k} expected 100, got {v}"


def test_compute_coverage_no_mantara() -> None:
    rep = compute_coverage(
        _cir(),
        mantara_schema=None,
        mantara_validation={"is_valid": False,
                              "errors": ["fail"] * 13},
    )
    # Mantara dropped — score should be very low
    assert rep.overall_score < 20.0
    assert rep.sub_scores["schema_completeness"] == 0.0


def test_compute_coverage_partial_real_world() -> None:
    """Realistic case: Mantara produced output but with some gaps."""
    cir = _cir()
    schema = _mantara_schema_partial()  # missing Item
    rep = compute_coverage(
        cir, mantara_schema=schema,
        mantara_sql="",
        mantara_validation={"is_valid": False,
                              "errors": ["error"] * 13},
    )
    # schema_completeness=50, mantara_validation=48, others=0/partial
    assert 0 < rep.overall_score < 100
    assert rep.sub_scores["schema_completeness"] == 50.0


# --------------------------------------------------------------------------- #
# Phase B improvements — accept rules-as-comments + discount upstream errors
# --------------------------------------------------------------------------- #
def test_rule_coverage_accepts_column_comment_in_json() -> None:
    """Phase B: rule must be detectable when it lives in a column comment in
    the Mantara JSON schema (not in raw SQL)."""
    cir = {"rules": [{"name": "min_one_item", "description": "must have items"}]}
    schema = {
        "menus": [{"submenus": [{"tables": [{
            "table_name": "order",
            "columns": [
                {"name": "id"},
                {"name": "items",
                 "comment": "RULE-001 min_one_item: every order must have ≥1 row"},
            ],
        }]}]}],
    }
    score, det = rule_constraint_coverage(cir, "", schema)
    assert score == 100.0


def test_rule_coverage_accepts_table_comment_in_json() -> None:
    cir = {"rules": [{"name": "all_skus_reserved",
                       "description": "all SKUs must be reserved before transition"}]}
    schema = {
        "menus": [{"submenus": [{"tables": [{
            "table_name": "asn",
            "comment": "Advanced Shipment Notice. Note: all_skus_reserved gate "
                        "before status transition.",
            "columns": [{"name": "id"}],
        }]}]}],
    }
    score, _ = rule_constraint_coverage(cir, "", schema)
    assert score == 100.0


def test_mantara_validation_discounts_upstream_thin_errors() -> None:
    """Phase B: upstream-thin-entity errors weighted at 0.5 each.

    13 errors with 6 upstream + 7 step5 → weighted = 7 + 6*0.5 = 10
      → score = 100 - 10*4 = 60 (vs 48 with the old 1:1 weighting)
    """
    errors = (
        ["THIN ENTITY: Table 'X' has only 4 business columns"] * 6
        + ["FK references undefined table"] * 4
        + ["Menu 'Y' has only 1 submenu"] * 3
    )
    score, det = mantara_validation_pass({"is_valid": False, "errors": errors})
    assert score == 60.0
    assert det["upstream_errors"] == 6
    assert det["step5_errors"] == 7
    assert det["weighted_errors"] == 10.0


def test_mantara_validation_handles_minimum_size_as_upstream() -> None:
    errors = ["MINIMUM SIZE: Schema has only 6 tables", "Menu has 1 submenu"]
    score, det = mantara_validation_pass({"is_valid": False, "errors": errors})
    # 1 upstream (0.5) + 1 step5 (1.0) = 1.5 weighted → 100 - 6 = 94
    assert score == 94.0


def test_rule_coverage_lenient_token_match() -> None:
    """Phase B: rule covered if half its distinctive tokens appear in the corpus.
    'door_assignment_required' has tokens ['assignment', 'required']; if SQL
    mentions 'door assignment' the rule is covered."""
    cir = {"rules": [{"name": "door_assignment_required",
                       "description": "A door must be assigned before transition"}]}
    sql = "ALTER TABLE asn ADD CONSTRAINT door_assignment_check ..."
    score, _ = rule_constraint_coverage(cir, sql, None)
    assert score == 100.0
