"""Phase 5 cross-stream integration tests — validates all streams work together."""

import hashlib

import pytest

from loom.auth.models import ROLE_PERMISSIONS, AgentRole
from loom.auth.store import create_agent, authenticate
from loom.auth.middleware import check_permission
from loom.dashboard.app import _get_all_tasks, _get_graph_mermaid, _handle_api_projects
from loom.graph import cache, store
from loom.graph.cache import _CircuitBreaker
from loom.graph.project import create_project, get_project_status, list_projects, archive_project
from loom.graph.task import Task, TaskStatus
from loom.observability.metrics import Metrics, metrics_handler


@pytest.fixture
async def two_projects(pool):
    """Create two separate projects for isolation testing."""
    p1 = await create_project(pool, "Project Alpha", "First project")
    p2 = await create_project(pool, "Project Beta", "Second project")
    return str(p1["id"]), str(p2["id"])


@pytest.fixture
async def multi_project_tasks(pool, two_projects):
    """Create tasks in both projects."""
    p1, p2 = two_projects
    t1 = Task(id="mp-001", project_id=p1, title="Alpha Task 1", status=TaskStatus.PENDING)
    t2 = Task(id="mp-002", project_id=p1, title="Alpha Task 2", status=TaskStatus.DONE)
    t3 = Task(id="mp-003", project_id=p2, title="Beta Task 1", status=TaskStatus.PENDING)
    t4 = Task(id="mp-004", project_id=p2, title="Beta Task 2", status=TaskStatus.PENDING, depends_on=["mp-003"])
    for t in [t1, t2, t3, t4]:
        await store.create_task(pool, t)
    return t1, t2, t3, t4


async def test_multi_project_with_optimized_queries(pool, two_projects, multi_project_tasks):
    """Multiple projects use batched dep loading."""
    p1, p2 = two_projects
    ready_p1 = await store.get_ready_tasks(pool, p1)
    ready_p2 = await store.get_ready_tasks(pool, p2)
    assert any(t.id == "mp-001" for t in ready_p1)
    assert not any(t.id == "mp-003" for t in ready_p1)  # Different project
    assert any(t.id == "mp-003" for t in ready_p2)


async def test_dashboard_shows_multiple_projects(pool, two_projects, multi_project_tasks):
    """Dashboard renders all projects via API."""
    response = await _handle_api_projects(pool)
    html = response.decode("utf-8")
    assert "Project Alpha" in html
    assert "Project Beta" in html


async def test_auth_blocks_cross_project_access(pool, two_projects):
    """Worker scoped to project A can't access project B."""
    p1, p2 = two_projects
    from loom.auth.models import Agent
    scoped_agent = Agent(
        id="agent-scoped", name="Scoped Worker", role=AgentRole.WORKER,
        project_ids=[p1], active=True,
    )
    assert check_permission(scoped_agent, "loom_claim", p1) is True
    assert check_permission(scoped_agent, "loom_claim", p2) is False


async def test_metrics_track_multi_project_ops(pool, two_projects, multi_project_tasks):
    """Metrics counters are per-project."""
    m = Metrics()
    p1, p2 = two_projects
    m.inc("test_p5_created", labels={"project": p1})
    m.inc("test_p5_created", labels={"project": p1})
    m.inc("test_p5_created", labels={"project": p2})

    output = metrics_handler()
    assert "test_p5_created" in output


async def test_circuit_breaker_metrics_reported():
    """CB state changes can be tracked."""
    cb = _CircuitBreaker(failure_threshold=2, cooldown_seconds=0.05)
    assert cb.state == "closed"
    cb.record_failure()
    cb.record_failure()
    assert cb.state == "open"
    # Metrics should track state
    m = Metrics()
    m.set_gauge("test_cb_state", 2)  # 2 = open
    assert m.get_gauge("test_cb_state") == 2


async def test_full_lifecycle_with_auth(pool, project):
    """Create agent → create task → claim → done, with auth checks."""
    # Create an agent
    agent = await create_agent(pool, "lifecycle-agent", "worker", "loom-lifecycle-key-123")
    assert agent.role == AgentRole.WORKER

    # Verify auth works
    authed = await authenticate(pool, "loom-lifecycle-key-123")
    assert authed is not None
    assert authed.id == agent.id

    # Check permissions
    assert check_permission(authed, "loom_claim", project) is True
    assert check_permission(authed, "loom_create", project) is False  # workers can't create

    # Create task (would be done by a lead/admin)
    task = Task(id="lifecycle-001", project_id=project, title="Full Lifecycle", status=TaskStatus.PENDING)
    await store.create_task(pool, task)

    # Claim
    claimed = await store.claim_task(pool, "lifecycle-001", agent.id, ttl_seconds=600)
    assert claimed.assignee == agent.id
    assert claimed.status == TaskStatus.CLAIMED

    # Complete
    completed = await store.complete_task(pool, "lifecycle-001", {"result": "done"})
    assert completed.status == TaskStatus.DONE


async def test_orchestrator_with_batched_queries(pool, project, multi_project_tasks):
    """Orchestrator-related functions use optimized queries."""
    # get_ready_tasks uses batch dep loading
    ready = await store.get_ready_tasks(pool, project)
    # All tasks in the fixture that are pending with no unsatisfied deps should be ready
    assert isinstance(ready, list)


async def test_graph_with_multi_project(pool, two_projects, multi_project_tasks):
    """Graph generation works per-project."""
    p1, p2 = two_projects
    graph_p1 = await _get_graph_mermaid(pool, p1)
    graph_p2 = await _get_graph_mermaid(pool, p2)
    assert "mp-001" in graph_p1
    assert "mp-003" not in graph_p1  # Belongs to project 2
    assert "mp-003" in graph_p2
    assert "mp-001" not in graph_p2


async def test_project_archive_clears_from_listing(pool, two_projects):
    """Archived projects don't appear in active list."""
    p1, p2 = two_projects
    await archive_project(pool, p1)
    projects = await list_projects(pool)
    project_ids = {str(p["id"]) for p in projects}
    assert p1 not in project_ids
    assert p2 in project_ids
