from __future__ import annotations

from datetime import datetime, timezone

import pytest
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from api.models.agent_message import AgentMessage
from api.models.fabric_graph import FabricGraphImport, FabricGraphNode
from api.models.interface_change import InterfaceChange
from api.models.managed_repo import ManagedRepo
from api.models.repository_rename import RepositorySlug
from tests.conftest import (
    create_test_domain,
    create_test_repo,
    create_test_workplan,
)


async def _rename_in_state_hub(test_engine, repo_id: str) -> None:
    factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        repo = await session.get(ManagedRepo, repo_id)
        old = (
            await session.execute(
                select(RepositorySlug).where(RepositorySlug.slug == "flex-auth")
            )
        ).scalar_one()
        old.kind = "alias"
        old.protected = True
        repo.slug = "access-engine"
        repo.name = "Access Engine"
        session.add(
            RepositorySlug(
                repo_id=repo.id,
                slug="access-engine",
                kind="canonical",
                protected=True,
            )
        )
        await session.commit()


@pytest.mark.asyncio
async def test_alias_lookup_dispatch_bindings_and_external_staleness(
    client, test_engine
):
    domain = await create_test_domain(client)
    repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
    workplan = await create_test_workplan(
        client,
        repo_id=repo["id"],
        slug="FLEX-WP-0001",
        title="Security stack migration",
    )
    task = await client.post(
        "/tasks/",
        json={
            "workplan_id": workplan["id"],
            "title": "Preserve identity",
            "status": "todo",
            "priority": "high",
        },
    )
    assert task.status_code == 201, task.text

    await _rename_in_state_hub(test_engine, repo["id"])
    factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        change = InterfaceChange(
            repo_id=repo["id"],
            interface_type="rest_api",
            change_type="breaking",
            title="Historical consumer notice",
            description="Recorded before cutover",
            affected_paths=["/authorize"],
            affected_repo_slugs=["flex-auth"],
            status="published",
            published_at=datetime.now(timezone.utc),
            author="pytest",
        )
        import_run = FabricGraphImport(
            source_repo_slug="railiance-fabric",
            content_hash="a" * 64,
            graph_json={},
            validation_status="valid",
            is_latest=True,
        )
        session.add_all([change, import_run])
        await session.flush()
        session.add(
            FabricGraphNode(
                import_id=import_run.id,
                source_repo_slug="railiance-fabric",
                graph_id="repo:flex-auth",
                kind="repository",
                name="Flex Auth",
                repo_slug="flex-auth",
                domain_slug="infotech",
                lifecycle="active",
            )
        )
        await session.commit()

    old_lookup = await client.get("/repos/flex-auth")
    new_lookup = await client.get("/repos/access-engine")
    assert old_lookup.status_code == new_lookup.status_code == 200
    assert old_lookup.json()["id"] == new_lookup.json()["id"] == repo["id"]
    assert old_lookup.json()["requested_slug"] == "flex-auth"
    assert old_lookup.json()["canonical_slug"] == "access-engine"
    assert old_lookup.json()["slug_status"] == "alias"
    assert old_lookup.json()["aliases"] == ["flex-auth"]
    assert new_lookup.json()["slug_status"] == "canonical"
    assert old_lookup.json()["stale_external_references"] == [
        {
            "owner": "railiance-fabric",
            "surface": "fabric_graph_nodes",
            "field": "repo_slug",
            "value": "flex-auth",
            "count": 1,
            "status": "stale",
            "handoff": "owner update or re-ingest required",
        }
    ]

    dispatch = await client.get("/repos/access-engine/dispatch")
    assert dispatch.status_code == 200, dispatch.text
    assert dispatch.json()["repo_slug"] == "access-engine"
    assert dispatch.json()["pending_interface_changes"][0]["title"] == change.title

    bound = await client.get(f"/workplans/{workplan['id']}")
    assert bound.status_code == 200
    assert bound.json()["id"] == workplan["id"]
    assert bound.json()["repo_id"] == repo["id"]
    bound_task = await client.get(f"/tasks/{task.json()['id']}")
    assert bound_task.status_code == 200
    assert bound_task.json()["id"] == task.json()["id"]


@pytest.mark.asyncio
async def test_message_history_is_immutable_and_old_slug_write_replays_once(
    client, test_engine
):
    domain = await create_test_domain(client)
    repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
    await _rename_in_state_hub(test_engine, repo["id"])
    factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        historical = AgentMessage(
            from_agent="security-review",
            to_agent="flex-auth",
            subject="Before cutover",
            body="Historical recipient must not be rewritten",
        )
        session.add(historical)
        await session.commit()
        historical_id = historical.id

    history = await client.get("/messages/", params={"to_agent": "access-engine"})
    assert history.status_code == 200
    assert [item["id"] for item in history.json()] == [str(historical_id)]
    assert history.json()[0]["to_agent"] == "flex-auth"

    payload = {
        "from_agent": "flex-auth",
        "to_agent": "flex-auth",
        "subject": "Queued during cutover",
        "body": "Replay exactly once",
    }
    headers = {"Idempotency-Key": "repo-rename-old-slug-message"}
    first = await client.post("/messages/", json=payload, headers=headers)
    replay = await client.post("/messages/", json=payload, headers=headers)
    assert first.status_code == replay.status_code == 201
    assert first.json()["id"] == replay.json()["id"]
    assert first.json()["from_agent"] == "access-engine"
    assert first.json()["to_agent"] == "access-engine"

    async with factory() as session:
        assert await session.scalar(
            select(func.count())
            .select_from(AgentMessage)
            .where(AgentMessage.subject == "Queued during cutover")
        ) == 1
        unchanged = await session.get(AgentMessage, historical_id)
        assert unchanged.to_agent == "flex-auth"


@pytest.mark.asyncio
async def test_alias_aware_interface_catalog_sbom_and_telemetry_reads(
    client, test_engine
):
    domain = await create_test_domain(client)
    repo = await create_test_repo(client, domain_slug=domain["slug"], slug="flex-auth")
    await _rename_in_state_hub(test_engine, repo["id"])

    interface = await client.post(
        "/interface-changes/",
        json={
            "repo_slug": "flex-auth",
            "interface_type": "rest_api",
            "change_type": "additive",
            "title": "Canonical write",
            "description": "New references use the current slug",
            "affected_repo_slugs": ["flex-auth"],
        },
    )
    assert interface.status_code == 201, interface.text
    assert interface.json()["repo_slug"] == "access-engine"
    assert interface.json()["affected_repo_slugs"] == ["access-engine"]
    affected = await client.get(
        "/interface-changes/", params={"affected_repo": "flex-auth"}
    )
    assert [item["id"] for item in affected.json()] == [interface.json()["id"]]

    goal = await client.post(
        "/repo-goals/",
        json={
            "repo_id": repo["id"],
            "title": "Preserve authorization continuity",
            "description": "Keep Net Kingdom consumers online",
        },
    )
    assert goal.status_code == 201, goal.text
    goals = await client.get("/repo-goals/", params={"repo_slug": "flex-auth"})
    assert [item["id"] for item in goals.json()] == [goal.json()["id"]]
    assert goals.json()[0]["repo_slug"] == "access-engine"

    capability = await client.post(
        "/capability-catalog/",
        json={
            "domain": "infotech",
            "repo_slug": "flex-auth",
            "capability_type": "authorization",
            "title": "Policy decisions",
            "description": "Net Kingdom authorization",
            "keywords": ["authorize"],
        },
    )
    assert capability.status_code == 201, capability.text
    catalog = await client.get(
        "/capability-catalog/", params={"repo_slug": "access-engine"}
    )
    assert [item["id"] for item in catalog.json()] == [capability.json()["id"]]
    assert catalog.json()[0]["repo_slug"] == "access-engine"

    service = await client.post(
        "/services/catalog",
        json={
            "slug": "access-engine-api",
            "name": "Access Engine API",
            "hosting_type": "self_hosted",
            "development_type": "first_party",
            "first_party": {"repo_slug": "flex-auth", "owning_domain": "infotech"},
            "self_hosted": {},
        },
    )
    assert service.status_code == 201, service.text
    services = await client.get("/services/catalog", params={"repo_slug": "flex-auth"})
    assert [item["id"] for item in services.json()] == [service.json()["id"]]

    ingested = await client.post(
        "/sbom/ingest/",
        json={
            "repo_slug": "flex-auth",
            "entries": [
                {
                    "package_name": "opa",
                    "package_version": "1.0",
                    "ecosystem": "go",
                }
            ],
        },
    )
    assert ingested.status_code == 200, ingested.text
    assert ingested.json()["repo_slug"] == "access-engine"
    sbom = await client.get("/sbom/flex-auth")
    assert sbom.status_code == 200
    assert sbom.json()["repo_slug"] == "access-engine"

    event = await client.post(
        "/token-events/",
        json={"repo_id": repo["id"], "tokens_in": 12, "tokens_out": 3},
    )
    assert event.status_code == 201, event.text
    summary = await client.get(
        "/token-events/by-repo/", params={"repo_slug": "flex-auth"}
    )
    assert summary.status_code == 200
    assert len(summary.json()) == 1
    assert summary.json()[0]["repo_slug"] == "access-engine"
    assert summary.json()[0]["tokens_total"] == 15
