from __future__ import annotations

import json
import os
from datetime import datetime, timezone
from pathlib import Path


async def _create_domain(client, slug="legacy-domain", name="Legacy Domain"):
    r = await client.post("/domains/", json={"slug": slug, "name": name})
    assert r.status_code == 201, r.text
    return r.json()


async def _create_topic(client, domain_slug="legacy-domain", slug="legacy-topic", title="Legacy Topic"):
    r = await client.post("/topics/", json={
        "slug": slug,
        "title": title,
        "domain": domain_slug,
    })
    assert r.status_code == 201, r.text
    return r.json()


async def _create_repo(client, domain_slug="legacy-domain", slug="legacy-repo"):
    r = await client.post("/repos/", json={
        "domain_slug": domain_slug,
        "slug": slug,
        "name": "Legacy Repo",
    })
    assert r.status_code == 201, r.text
    return r.json()


async def _create_workplan(client, topic_id, domain_slug="legacy-domain", slug="legacy-wp", title="Legacy WP"):
    repo = await _create_repo(client, domain_slug=domain_slug, slug=f"{slug}-repo")
    r = await client.post("/workplans/", json={
        "repo_id": repo["id"],
        "topic_id": topic_id,
        "slug": slug,
        "title": title,
        "status": "ready",
    })
    assert r.status_code == 201, r.text
    return r.json()


def _summary_by_key(summary):
    return {item["interface"]["interface_key"]: item for item in summary["interfaces"]}


class TestWorkplanAliasesAndLegacyMeter:
    async def test_preferred_workplan_routes_do_not_meter_legacy_usage(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.get(f"/workplans/?topic_id={topic['id']}")
        assert r.status_code == 200
        assert [row["id"] for row in r.json()] == [wp["id"]]

        r = await client.patch(f"/workplans/{wp['id']}", json={"status": "active"})
        assert r.status_code == 200
        assert r.json()["status"] == "active"

        r = await client.get("/legacy-meter/summary")
        assert r.status_code == 200
        assert r.json()["interfaces"] == []

    async def test_legacy_get_workstreams_returns_410_and_meters(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        await _create_workplan(client, topic["id"])

        r = await client.get(
            f"/workstreams/?topic_id={topic['id']}",
            headers={
                "X-StateHub-Tenant": "tenant-a",
                "X-StateHub-User": "alice",
                "X-StateHub-Component": "old-client",
            },
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["Deprecation"] == "true"
        assert r.headers.get("Sunset")
        assert r.headers["X-StateHub-Replacement"] == "/workplans/"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:GET /workstreams/"]
        assert item["window"]["calls"] == 1
        assert item["window"]["tenants"] == {"tenant-a": 1}
        assert item["window"]["users"] == {"alice": 1}
        assert item["window"]["components"] == {"old-client": 1}

    async def test_weekly_review_accepts_hours_window(self, client):
        r = await client.post("/legacy-meter/interfaces", json={
            "interface_key": "rest_api:GET /hour-window",
            "interface_kind": "rest_api",
            "replacement_ref": "/workplans/",
            "replacement_verified": True,
        })
        assert r.status_code == 201, r.text

        review = (await client.get("/legacy-meter/weekly-review?hours=8")).json()
        window_seconds = (
            datetime.fromisoformat(review["window_end"].replace("Z", "+00:00"))
            - datetime.fromisoformat(review["window_start"].replace("Z", "+00:00"))
        ).total_seconds()
        assert 8 * 3600 - 5 <= window_seconds <= 8 * 3600 + 5

        bad = await client.get("/legacy-meter/weekly-review?days=7&hours=8")
        assert bad.status_code == 422

    async def test_weekly_review_reports_unused_verified_legacy_interface(self, client):
        r = await client.post("/legacy-meter/interfaces", json={
            "interface_key": "rest_api:GET /obsolete",
            "interface_kind": "rest_api",
            "replacement_ref": "/workplans/",
            "replacement_verified": True,
        })
        assert r.status_code == 201, r.text

        review = (await client.get("/legacy-meter/weekly-review")).json()
        assert review["activity_core_handoff"]["scheduler_owner"] == "activity-core"
        assert review["activity_core_handoff"]["source_endpoint"] == "/legacy-meter/weekly-review"
        assert "window_start" in review
        assert "window_end" in review
        assert isinstance(review["interfaces"], list)
        assert isinstance(review["retirement_candidates"], list)
        candidates = _summary_by_key({"interfaces": review["retirement_candidates"]})
        assert "rest_api:GET /obsolete" in candidates
        assert candidates["rest_api:GET /obsolete"]["retirement_reason"] == "no measured usage in review window"

        if os.environ.get("CAPTURE_LEGACY_METER_EVIDENCE"):
            evidence_dir = Path(__file__).resolve().parents[1] / "docs" / "evidence"
            evidence_dir.mkdir(parents=True, exist_ok=True)
            today = datetime.now(tz=timezone.utc).strftime("%Y%m%d")
            out = evidence_dir / f"legacy-meter-weekly-review-{today}.json"
            payload = {
                "captured_at": datetime.now(tz=timezone.utc).isoformat(),
                "source": "pytest",
                "workplan": "STATE-WP-0069",
                "weekly_review": review,
            }
            out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")

    async def test_recent_usage_blocks_weekly_retirement_candidate(self, client):
        r = await client.post("/legacy-meter/usage", json={
            "interface_key": "rest_api:GET /old-but-used",
            "interface_kind": "rest_api",
            "replacement_ref": "/workplans/",
            "replacement_verified": True,
            "tenant_key": "tenant-a",
            "user_key": "alice",
            "component_key": "old-client",
        })
        assert r.status_code == 201, r.text

        review = (await client.get("/legacy-meter/weekly-review")).json()
        items = _summary_by_key(review)
        item = items["rest_api:GET /old-but-used"]
        assert item["window"]["calls"] == 1
        assert item["retirement_candidate"] is False
        assert item["retirement_reason"] == "1 call(s) in review window"

    async def test_legacy_patch_workstream_returns_410_and_meters(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.patch(
            f"/workstreams/{wp['id']}",
            json={"status": "finished"},
            headers={"X-StateHub-Component": "old-client"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]

        summary = (await client.get("/legacy-meter/summary")).json()
        items = _summary_by_key(summary)
        assert items["rest_api:PATCH /workstreams/{workstream_id}"]["window"]["components"] == {
            "old-client": 1
        }

    async def test_legacy_workplan_index_returns_410(self, client):
        r = await client.get(
            "/workstreams/workplan-index",
            headers={"X-StateHub-Component": "old-index-client"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["X-StateHub-Replacement"] == "/workplans/index"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:GET /workstreams/workplan-index"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-index-client": 1}

    async def test_legacy_delete_workstream_returns_410(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.delete(
            f"/workstreams/{wp['id']}",
            headers={"X-StateHub-Component": "old-archiver"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["Deprecation"] == "true"
        assert r.headers["X-StateHub-Replacement"] == "DELETE /workplans/{workplan_id}"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:DELETE /workstreams/{workstream_id}"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-archiver": 1}

    async def test_legacy_workstream_id_query_param_on_tasks_is_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])
        task_r = await client.post("/tasks/", json={
            "workplan_id": wp["id"],
            "title": "Meter me",
            "status": "todo",
        })
        assert task_r.status_code == 201, task_r.text

        r = await client.get(
            f"/tasks/?workstream_id={wp['id']}",
            headers={
                "X-StateHub-Tenant": "tenant-a",
                "X-StateHub-User": "alice",
                "X-StateHub-Component": "old-task-client",
            },
        )
        assert r.status_code == 200
        assert r.headers["Deprecation"] == "true"
        assert r.headers["X-StateHub-Replacement"] == "/tasks/?workplan_id=<workplan_id>"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:GET /tasks/?workstream_id"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-task-client": 1}

    async def test_legacy_workstream_id_query_param_on_progress_is_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.get(
            f"/progress/?workstream_id={wp['id']}",
            headers={"X-StateHub-Component": "old-progress-client"},
        )
        assert r.status_code == 200
        assert r.headers["Deprecation"] == "true"
        assert r.headers["X-StateHub-Replacement"] == "/progress/?workplan_id=<workplan_id>"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:GET /progress/?workstream_id"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-progress-client": 1}

    async def test_legacy_workstream_id_body_on_progress_post_is_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.post(
            "/progress/",
            json={
                "workstream_id": str(wp["id"]),
                "event_type": "note",
                "summary": "legacy body field",
                "author": "test",
            },
            headers={"X-StateHub-Component": "old-progress-writer"},
        )
        assert r.status_code == 201
        assert r.headers["Deprecation"] == "true"
        assert r.headers["X-StateHub-Replacement"] == "POST /progress/ with workplan_id"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:POST /progress/ workstream_id"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-progress-writer": 1}

    async def test_workplan_id_body_on_progress_post_is_not_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.post(
            "/progress/",
            json={
                "workplan_id": str(wp["id"]),
                "event_type": "note",
                "summary": "preferred body field",
                "author": "test",
            },
        )
        assert r.status_code == 201
        assert r.headers.get("Deprecation") != "true"

        summary = (await client.get("/legacy-meter/summary")).json()
        assert _summary_by_key(summary).get("rest_api:POST /progress/ workstream_id") is None

    async def test_legacy_workstream_id_body_on_tasks_post_is_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.post(
            "/tasks/",
            json={
                "workstream_id": str(wp["id"]),
                "title": "legacy task body",
                "status": "todo",
            },
            headers={"X-StateHub-Component": "old-task-writer"},
        )
        assert r.status_code == 201
        assert r.headers["Deprecation"] == "true"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:POST /tasks/ workstream_id"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-task-writer": 1}

    async def test_legacy_workstream_id_body_on_decisions_post_is_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.post(
            "/decisions/",
            json={
                "workstream_id": str(wp["id"]),
                "title": "legacy decision body",
                "decision_type": "pending",
            },
            headers={"X-StateHub-Component": "old-decision-writer"},
        )
        assert r.status_code == 201
        assert r.headers["Deprecation"] == "true"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:POST /decisions/ workstream_id"]
        assert item["window"]["calls"] == 1

    async def test_workplan_id_query_param_on_tasks_is_not_metered(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.get(f"/tasks/?workplan_id={wp['id']}")
        assert r.status_code == 200
        assert r.headers.get("Deprecation") != "true"

        summary = (await client.get("/legacy-meter/summary")).json()
        assert _summary_by_key(summary).get("rest_api:GET /tasks/?workstream_id") is None

    async def test_workplan_dependency_and_execution_aliases(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        first = await _create_workplan(client, topic["id"], slug="first", title="First")
        second = await _create_workplan(client, topic["id"], slug="second", title="Second")

        r = await client.post(
            f"/workplans/{first['id']}/dependencies/",
            json={"to_workstream_id": second["id"]},
        )
        assert r.status_code == 201, r.text

        r = await client.get(f"/workplans/{first['id']}/dependencies/")
        assert r.status_code == 200
        assert len(r.json()) == 1

        r = await client.patch(
            f"/execution/workplans/{first['id']}/intent",
            json={"execution_state": "queued", "launch_mode": "queued"},
        )
        assert r.status_code == 200
        assert r.json()["execution_state"] == "queued"

        r = await client.patch(
            f"/execution/workstreams/{first['id']}/intent",
            json={"execution_state": "manual", "launch_mode": "manual"},
            headers={"X-StateHub-Component": "old-executor"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["X-StateHub-Replacement"] == "/execution/workplans/{workplan_id}/intent"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:PATCH /execution/workstreams/{workstream_id}/intent"]
        assert item["window"]["components"] == {"old-executor": 1}

    async def test_legacy_post_workstream_returns_410_and_meters(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        repo = await _create_repo(client, slug="legacy-create-repo")

        r = await client.post(
            "/workstreams/",
            json={
                "repo_id": str(repo["id"]),
                "topic_id": str(topic["id"]),
                "slug": "legacy-create",
                "title": "Legacy Create",
                "status": "active",
            },
            headers={"X-StateHub-Component": "old-creator"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["X-StateHub-Replacement"] == "/workplans/"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:POST /workstreams/"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-creator": 1}

    async def test_legacy_get_workstream_detail_returns_410_and_meters(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        wp = await _create_workplan(client, topic["id"])

        r = await client.get(
            f"/workstreams/{wp['id']}",
            headers={"X-StateHub-Component": "old-reader"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]
        assert r.headers["X-StateHub-Replacement"] == "/workplans/{workplan_id}"

        summary = (await client.get("/legacy-meter/summary")).json()
        item = _summary_by_key(summary)["rest_api:GET /workstreams/{workstream_id}"]
        assert item["window"]["calls"] == 1
        assert item["window"]["components"] == {"old-reader": 1}

    async def test_legacy_workstream_dependency_routes_return_410_and_meter(self, client):
        await _create_domain(client)
        topic = await _create_topic(client)
        first = await _create_workplan(client, topic["id"], slug="dep-first", title="First")
        second = await _create_workplan(client, topic["id"], slug="dep-second", title="Second")

        dep = await client.post(
            f"/workplans/{first['id']}/dependencies/",
            json={"to_workplan_id": second["id"]},
        )
        assert dep.status_code == 201, dep.text
        dep_id = dep.json()["id"]

        r = await client.post(
            f"/workstreams/{first['id']}/dependencies/",
            json={"to_workstream_id": second["id"]},
            headers={"X-StateHub-Component": "old-dep-writer"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]

        r = await client.get(
            f"/workstreams/{first['id']}/dependencies/",
            headers={"X-StateHub-Component": "old-dep-reader"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]

        r = await client.delete(
            f"/workstreams/{first['id']}/dependencies/{dep_id}",
            headers={"X-StateHub-Component": "old-dep-archiver"},
        )
        assert r.status_code == 410
        assert "retired" in r.json()["detail"]

        summary = (await client.get("/legacy-meter/summary")).json()
        items = _summary_by_key(summary)
        assert items["rest_api:POST /workstreams/{workstream_id}/dependencies/"]["window"]["components"] == {
            "old-dep-writer": 1
        }
        assert items["rest_api:GET /workstreams/{workstream_id}/dependencies/"]["window"]["components"] == {
            "old-dep-reader": 1
        }
        assert items["rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}"]["window"]["components"] == {
            "old-dep-archiver": 1
        }


# --- retirement quiet-period ladder (STATE-WP-0079-T05) ---------------------
#
# A clean review window only proves silence for that window. For an interface
# that carried heavy traffic, that is weak evidence: an infrequent caller can
# fall outside the window entirely. The ladder requires silence proportional to
# all-time volume before an interface becomes a retirement candidate.


def _iface(**kw):
    from types import SimpleNamespace

    defaults = dict(
        status="legacy",
        manual_hold=False,
        hold_reason=None,
        replacement_ref="replacement",
        replacement_verified=True,
    )
    defaults.update(kw)
    return SimpleNamespace(**defaults)


def test_required_quiet_days_scales_with_volume():
    from api.services.legacy_meter import _required_quiet_days

    assert _required_quiet_days(0) == 0
    assert _required_quiet_days(1) == 7
    assert _required_quiet_days(99) == 7
    assert _required_quiet_days(100) == 30
    assert _required_quiet_days(9_999) == 30
    assert _required_quiet_days(10_000) == 60
    assert _required_quiet_days(511_406) == 60


def test_high_volume_interface_held_until_quiet_period_elapses():
    """A six-figure interface quiet for 9 days is not yet retirable."""
    from api.services.legacy_meter import _retirement_state

    window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
    candidate, reason = _retirement_state(
        _iface(),
        0,
        all_time_calls=144_086,
        last_seen_at=datetime(2026, 8, 11, tzinfo=timezone.utc),
        window_end=window_end,
    )
    assert candidate is False
    assert "9d of 60d required" in reason


def test_high_volume_interface_retirable_once_quiet_long_enough():
    from api.services.legacy_meter import _retirement_state

    window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
    candidate, reason = _retirement_state(
        _iface(),
        0,
        all_time_calls=144_086,
        last_seen_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
        window_end=window_end,
    )
    assert candidate is True
    assert "60d required" in reason


def test_low_volume_interface_uses_short_quiet_period():
    from api.services.legacy_meter import _retirement_state

    window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
    candidate, _ = _retirement_state(
        _iface(),
        0,
        all_time_calls=2,
        last_seen_at=datetime(2026, 8, 12, tzinfo=timezone.utc),
        window_end=window_end,
    )
    assert candidate is True


def test_never_used_interface_needs_no_quiet_period():
    from api.services.legacy_meter import _retirement_state

    candidate, reason = _retirement_state(
        _iface(),
        0,
        all_time_calls=0,
        last_seen_at=None,
        window_end=datetime(2026, 8, 20, tzinfo=timezone.utc),
    )
    assert candidate is True
    assert reason == "no measured usage in review window"


def test_in_window_traffic_still_wins_over_quiet_ladder():
    """Traffic inside the window blocks retirement regardless of volume."""
    from api.services.legacy_meter import _retirement_state

    candidate, reason = _retirement_state(
        _iface(),
        3,
        all_time_calls=5,
        last_seen_at=datetime(2026, 8, 19, tzinfo=timezone.utc),
        window_end=datetime(2026, 8, 20, tzinfo=timezone.utc),
    )
    assert candidate is False
    assert "3 call(s) in review window" in reason


def test_existing_guards_precede_quiet_ladder():
    from api.services.legacy_meter import _retirement_state

    window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
    long_quiet = dict(
        all_time_calls=1,
        last_seen_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
        window_end=window_end,
    )
    assert _retirement_state(_iface(status="retired"), 0, **long_quiet) == (
        False,
        "already retired",
    )
    assert _retirement_state(
        _iface(manual_hold=True, hold_reason="pinned"), 0, **long_quiet
    ) == (False, "pinned")
    assert _retirement_state(_iface(replacement_ref="  "), 0, **long_quiet) == (
        False,
        "missing replacement reference",
    )
    assert _retirement_state(_iface(replacement_verified=False), 0, **long_quiet) == (
        False,
        "replacement not verified",
    )
