import asyncio
import uuid

from app.models import (
    Agent,
    AgentDelegation,
    AgentPermission,
    AgentVersion,
    AuditEvent,
    Computer,
    ComputerProfile,
    User,
    Workspace,
    WorkspaceMembership,
)
from sqlalchemy import func, select


def owner_payload():
    return {
        "email": "owner@example.com",
        "password": "correct horse battery staple",
        "display_name": "Owner",
        "workspace_name": "Personal",
        "timezone": "Asia/Dubai",
    }


def csrf_headers(client):
    return {"x-csrf-token": client.cookies.get("hayva_csrf")}


def agent_payload(name="Research Assistant", manager_agent_id=None):
    return {
        "name": name,
        "description": "Read-only workspace researcher",
        "purpose": "Inspect authorized workspace state.",
        "system_instructions": "Use only verified read tools.",
        "personality": "Careful and concise.",
        "autonomy_level": 1,
        "preferred_model": None,
        "fallback_model": None,
        "manager_agent_id": manager_agent_id,
        "budget_limit_minor": 2500,
        "budget_currency": "USD",
        "execution_limit": 20,
        "tool_names": ["workspace.profile.read"],
        "permission_keys": ["workspace.profile.read"],
        "schedule": {
            "timezone": "Asia/Dubai",
            "working_days": ["mon", "tue", "wed", "thu", "fri"],
            "start_minute": 540,
            "end_minute": 1020,
        },
    }


def mark_computers_provisioned(client, agent_ids: list[str]):
    async def update_records():
        async with client.session_factory() as session:
            for agent_id in agent_ids:
                agent = await session.get(Agent, uuid.UUID(agent_id))
                computer = await session.get(Computer, agent.computer_id)
                profile = await session.scalar(select(ComputerProfile).where(
                    ComputerProfile.agent_id == agent.id
                ))
                computer.status = "stopped"
                computer.provider_resource_id = f"test-vm-{computer.id}"
                computer.compute_provider = "test"
                profile.status = "disabled"
            await session.commit()

    asyncio.run(update_records())


def test_owner_setup_creates_scoped_default_agent(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    listed = auth_client.get("/api/v1/agents")
    assert listed.status_code == 200
    assert len(listed.json()["agents"]) == 1
    default = listed.json()["agents"][0]
    assert default["name"] == "Personal Executive Assistant"
    assert default["status"] == "disabled"
    assert default["autonomy_level"] == 3
    assert default["computer_required"] is True
    assert default["computer"]["status"] == "unconfigured"

    detail = auth_client.get(f"/api/v1/agents/{default['id']}").json()["agent"]
    assert set(detail["tool_names"]) == {
        "system.status.read", "workspace.profile.read", "browser.observe",
        "browser.navigate", "browser.wait_for", "browser.extract", "browser.tab_list",
        "browser.screenshot", "browser.click", "browser.type", "browser.press",
        "browser.tab_open", "browser.tab_switch", "browser.tab_close",
    }
    assert {"computer.observe", "computer.control"} <= set(detail["permission_keys"])
    assert {"email.read", "calendar.read", "whatsapp.read", "memory.read"} <= set(
        detail["permission_keys"]
    )


def test_agent_builder_versions_duplicates_and_lifecycle(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    no_csrf = auth_client.post("/api/v1/agents", json=agent_payload())
    assert no_csrf.status_code == 403
    created_response = auth_client.post(
        "/api/v1/agents", json=agent_payload(), headers=csrf_headers(auth_client)
    )
    assert created_response.status_code == 201
    created = created_response.json()["agent"]
    assert created["status"] == "disabled"
    assert created["version"] == 1
    assert created["schedule"]["working_days"] == ["mon", "tue", "wed", "thu", "fri"]

    updated = auth_client.patch(
        f"/api/v1/agents/{created['id']}",
        json={"description": "Updated", "autonomy_level": 4},
        headers=csrf_headers(auth_client),
    )
    assert updated.status_code == 200
    assert updated.json()["agent"]["version"] == 2
    assert updated.json()["agent"]["description"] == "Updated"

    blocked = auth_client.post(
        f"/api/v1/agents/{created['id']}/status",
        json={"status": "active"},
        headers=csrf_headers(auth_client),
    )
    assert blocked.status_code == 409
    assert blocked.json()["error"]["code"] == "AGENT_DEDICATED_COMPUTER_UNAVAILABLE"
    mark_computers_provisioned(auth_client, [created["id"]])

    enabled = auth_client.post(
        f"/api/v1/agents/{created['id']}/status",
        json={"status": "active"},
        headers=csrf_headers(auth_client),
    )
    assert enabled.status_code == 200
    assert enabled.json()["agent"]["status"] == "active"

    duplicated = auth_client.post(
        f"/api/v1/agents/{created['id']}/duplicate",
        json={"name": "Research Assistant Copy"},
        headers=csrf_headers(auth_client),
    )
    assert duplicated.status_code == 201
    assert duplicated.json()["agent"]["name"] == "Research Assistant Copy"
    assert duplicated.json()["agent"]["status"] == "disabled"

    async def version_count():
        async with auth_client.session_factory() as session:
            return await session.scalar(select(func.count(AgentVersion.id)).where(
                AgentVersion.agent_id == uuid.UUID(created["id"])
            ))

    assert asyncio.run(version_count()) == 3


def test_agent_capability_escalation_hierarchy_cycle_and_workspace_scope_are_denied(auth_client):
    setup = auth_client.post("/api/v1/setup/owner", json=owner_payload()).json()
    escalation = agent_payload("Escalation Attempt")
    escalation["permission_keys"] = ["not.real"]
    rejected = auth_client.post(
        "/api/v1/agents", json=escalation, headers=csrf_headers(auth_client)
    )
    assert rejected.status_code == 422
    assert rejected.json()["error"]["code"] == "AGENT_PERMISSION_UNKNOWN"

    parent = auth_client.post(
        "/api/v1/agents", json=agent_payload("Parent"), headers=csrf_headers(auth_client)
    ).json()["agent"]
    child = auth_client.post(
        "/api/v1/agents",
        json=agent_payload("Child", parent["id"]),
        headers=csrf_headers(auth_client),
    ).json()["agent"]
    cycle = auth_client.patch(
        f"/api/v1/agents/{parent['id']}",
        json={"manager_agent_id": child["id"]},
        headers=csrf_headers(auth_client),
    )
    assert cycle.status_code == 409
    assert cycle.json()["error"]["code"] == "AGENT_RELATIONSHIP_CYCLE"

    other_agent_id = uuid.uuid4()

    async def create_other_workspace_agent():
        async with auth_client.session_factory() as session:
            user = await session.scalar(select(User))
            workspace = Workspace(id=uuid.uuid4(), name="Other", timezone="UTC")
            session.add(workspace)
            await session.flush()
            session.add(WorkspaceMembership(
                id=uuid.uuid4(), workspace_id=workspace.id, user_id=user.id, status="active"
            ))
            await session.flush()
            session.add(Agent(
                id=other_agent_id, workspace_id=workspace.id, created_by_user_id=user.id,
                name="Other Private Agent", description="", purpose="", status="disabled",
                autonomy_level=1, budget_currency="USD", version=1,
            ))
            await session.commit()

    asyncio.run(create_other_workspace_agent())
    assert setup["workspace_id"]
    inaccessible = auth_client.get(f"/api/v1/agents/{other_agent_id}")
    assert inaccessible.status_code == 404


def test_delegation_is_limited_to_actor_and_both_agent_permissions(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    source = auth_client.post(
        "/api/v1/agents", json=agent_payload("Source"), headers=csrf_headers(auth_client)
    ).json()["agent"]
    target = auth_client.post(
        "/api/v1/agents", json=agent_payload("Target"), headers=csrf_headers(auth_client)
    ).json()["agent"]
    for agent in (source, target):
        mark_computers_provisioned(auth_client, [agent["id"]])
        response = auth_client.post(
            f"/api/v1/agents/{agent['id']}/status",
            json={"status": "active"}, headers=csrf_headers(auth_client),
        )
        assert response.status_code == 200

    delegated = auth_client.post(
        f"/api/v1/agents/{source['id']}/delegations",
        json={
            "target_agent_id": target["id"],
            "instruction": "Read the workspace profile.",
            "permission_scope": ["workspace.profile.read"],
        },
        headers=csrf_headers(auth_client),
    )
    assert delegated.status_code == 201
    assert delegated.json()["delegation"]["permission_scope"] == ["workspace.profile.read"]

    invalid = auth_client.post(
        f"/api/v1/agents/{source['id']}/delegations",
        json={
            "target_agent_id": target["id"],
            "instruction": "Attempt a send.",
            "permission_scope": ["email.send"],
        },
        headers=csrf_headers(auth_client),
    )
    assert invalid.status_code == 403

    async def persisted_evidence():
        async with auth_client.session_factory() as session:
            delegation = await session.scalar(select(AgentDelegation))
            source_grants = set((await session.scalars(select(
                AgentPermission.permission_key
            ).where(AgentPermission.agent_id == uuid.UUID(source["id"])))).all())
            audit_types = set((await session.scalars(select(AuditEvent.event_type))).all())
            return delegation, source_grants, audit_types

    record, grants, audit_types = asyncio.run(persisted_evidence())
    assert record.instruction_hash != record.instruction
    assert record.permission_scope == ["workspace.profile.read"]
    assert grants == {"workspace.profile.read"}
    assert "agent.delegation_requested" in audit_types
