"""SLICE-1 unit tests — sidecar metadata: dual-read, unknown-key preservation,
frontmatter stripping, malformed detection.

Pure functions only (no DB / no Gitea). Per
docs/design/2026-06-06-configurable-collection-metadata.md §7.2 (SLICE-1) and
INV-6 (dual-read), INV-7 (unknown keys ride along), INV-2 (clean body).
"""
from __future__ import annotations

import yaml

from app import entry as entry_mod
from app import metadata


LEGACY_MD = """---
slug: view-metrics
title: View today's metrics
state: active
owners:
- ben.stull
tags:
- dashboard
- analytics
priority: P1
owner: hasan
---

This is the prose body.

Second paragraph.
"""


# ---- INV-7: unknown keys ride along ----

def test_parse_preserves_unknown_keys_in_extra():
    e = entry_mod.parse(LEGACY_MD)
    assert e.extra == {"priority": "P1", "owner": "hasan"}


def test_serialize_round_trip_preserves_unknown_keys():
    e = entry_mod.parse(LEGACY_MD)
    text = entry_mod.serialize(e)
    e2 = entry_mod.parse(text)
    assert e2.extra == {"priority": "P1", "owner": "hasan"}
    assert e2.tags == ["dashboard", "analytics"]
    assert e2.owners == ["ben.stull"]


def test_known_keys_never_leak_into_extra():
    e = entry_mod.parse(LEGACY_MD)
    for known in ("slug", "title", "state", "owners", "tags"):
        assert known not in e.extra


# ---- metadata_dict / sidecar_yaml ----

def test_metadata_dict_merges_known_and_extra():
    e = entry_mod.parse(LEGACY_MD)
    d = metadata.metadata_dict(e)
    assert d["slug"] == "view-metrics"
    assert d["title"] == "View today's metrics"
    assert d["state"] == "active"
    assert d["tags"] == ["dashboard", "analytics"]
    # forward-compat keys present
    assert d["priority"] == "P1"
    assert d["owner"] == "hasan"


def test_sidecar_yaml_is_parseable_and_has_no_frontmatter_fences():
    e = entry_mod.parse(LEGACY_MD)
    sc = metadata.sidecar_yaml(e)
    assert "---" not in sc.splitlines()[0]
    loaded = yaml.safe_load(sc)
    assert loaded["slug"] == "view-metrics"
    assert loaded["priority"] == "P1"


# ---- strip_frontmatter (INV-2) ----

def test_strip_frontmatter_removes_leading_block():
    body = metadata.strip_frontmatter(LEGACY_MD)
    assert body.startswith("This is the prose body.")
    assert "slug:" not in body
    assert "priority:" not in body


def test_strip_frontmatter_passthrough_when_no_frontmatter():
    plain = "Just a body.\n\nNo frontmatter here.\n"
    assert metadata.strip_frontmatter(plain).strip() == plain.strip()


# ---- parse_sidecar (malformed detection, INV-3) ----

def test_parse_sidecar_good():
    values, malformed = metadata.parse_sidecar("slug: a\ntitle: A\npriority: P0\n")
    assert malformed is False
    assert values == {"slug": "a", "title": "A", "priority": "P0"}


def test_parse_sidecar_non_mapping_is_malformed():
    values, malformed = metadata.parse_sidecar("- just\n- a\n- list\n")
    assert malformed is True
    assert values == {}


def test_parse_sidecar_invalid_yaml_is_malformed():
    values, malformed = metadata.parse_sidecar("slug: : : not yaml\n  bad: [unclosed\n")
    assert malformed is True
    assert values == {}


def test_parse_sidecar_empty_is_empty_not_malformed():
    values, malformed = metadata.parse_sidecar("")
    assert malformed is False
    assert values == {}


# ---- read_entry dual-read equivalence (INV-6) ----

def test_dual_read_sidecar_matches_legacy():
    legacy_entry, legacy_bad = metadata.read_entry(LEGACY_MD, None)

    # The migrated form: body-only .md + a sidecar holding the metadata.
    migrated_md = metadata.strip_frontmatter(LEGACY_MD)
    sidecar_text = metadata.sidecar_yaml(legacy_entry)
    sidecar_entry, sidecar_bad = metadata.read_entry(migrated_md, sidecar_text)

    assert legacy_bad is False
    assert sidecar_bad is False
    # Identical resulting records (INV-6).
    assert sidecar_entry.slug == legacy_entry.slug
    assert sidecar_entry.title == legacy_entry.title
    assert sidecar_entry.state == legacy_entry.state
    assert sidecar_entry.owners == legacy_entry.owners
    assert sidecar_entry.tags == legacy_entry.tags
    assert sidecar_entry.extra == legacy_entry.extra
    assert sidecar_entry.body.strip() == legacy_entry.body.strip()


def test_read_entry_sidecar_takes_precedence_over_md_frontmatter():
    # A not-yet-migrated .md still carrying frontmatter, plus a sidecar that
    # disagrees: the sidecar wins for metadata; the body comes from the .md.
    md_with_fm = "---\nslug: old\ntitle: Old Title\nstate: super-draft\n---\n\nBody.\n"
    sidecar = "slug: new\ntitle: New Title\nstate: active\n"
    e, malformed = metadata.read_entry(md_with_fm, sidecar)
    assert malformed is False
    assert e.title == "New Title"
    assert e.state == "active"
    assert e.body.strip() == "Body."


def test_read_entry_malformed_sidecar_still_loads_entry():
    # INV-3: a malformed sidecar never hard-fails the read. The entry loads
    # (from the .md frontmatter if present) and is flagged malformed.
    md = "---\nslug: x\ntitle: X\nstate: active\n---\n\nBody.\n"
    e, malformed = metadata.read_entry(md, "- not a mapping\n")
    assert malformed is True
    assert e.slug == "x"
    assert e.title == "X"
    assert e.body.strip() == "Body."


# ---- dual-read robustness: degenerate sidecars never drop the entry ----

def test_empty_sidecar_falls_back_to_md_frontmatter():
    # An empty sidecar has no metadata to override with — keep the .md's.
    md = "---\nslug: keep\ntitle: Keep Me\nstate: active\n---\n\nBody.\n"
    e, malformed = metadata.read_entry(md, "", fallback_slug="keep")
    assert malformed is False
    assert e.slug == "keep"
    assert e.title == "Keep Me"


def test_malformed_sidecar_on_body_only_md_loads_with_fallback_slug():
    # The .md is already body-only (migrated) and the sidecar is corrupt:
    # the entry must still load (INV-3), taking its slug from the filename stem.
    e, malformed = metadata.read_entry("Just a body.\n", "- a\n- list\n", fallback_slug="foo")
    assert malformed is True
    assert e.slug == "foo"


def test_slugless_sidecar_uses_fallback_slug():
    md = "Body only.\n"
    sidecar = "title: No Slug Here\nstate: active\n"
    e, malformed = metadata.read_entry(md, sidecar, fallback_slug="bar")
    assert malformed is False
    assert e.slug == "bar"
    assert e.title == "No Slug Here"


# ---- sidecar filename helpers ----

def test_sidecar_filename_helpers():
    assert metadata.sidecar_name("view-metrics") == "view-metrics.meta.yaml"
    assert metadata.is_sidecar("view-metrics.meta.yaml") is True
    assert metadata.is_sidecar("view-metrics.md") is False
    assert metadata.slug_of_sidecar("view-metrics.meta.yaml") == "view-metrics"


# ---- SLICE-4: sidecar_path_for + apply_values ----

def test_sidecar_path_for_derives_sibling():
    assert metadata.sidecar_path_for("rfcs/alpha.md") == "rfcs/alpha.meta.yaml"
    assert metadata.sidecar_path_for("x/y/beta.md") == "x/y/beta.meta.yaml"


def test_apply_values_updates_known_and_extra_fields():
    e = entry_mod.parse(LEGACY_MD)  # has tags + extra priority/owner
    e2 = metadata.apply_values(e, {"tags": ["x"], "priority": "P0", "owner": "sam"})
    assert e2.tags == ["x"]
    assert e2.extra["priority"] == "P0"
    assert e2.extra["owner"] == "sam"
    # body preserved unchanged
    assert e2.body == e.body


def test_apply_values_preserves_unspecified_keys():
    e = entry_mod.parse(LEGACY_MD)
    e2 = metadata.apply_values(e, {"priority": "P0"})
    assert e2.tags == e.tags            # untouched
    assert e2.extra["owner"] == "hasan" # untouched
