import asyncio
import uuid

from app.ai_client import AIPlan, AIPlanResult, AIPlanStep, AIUsage, get_ai_plan_client
from app.errors import AppError
from app.main import app
from app.models import (
    Agent,
    AuditEvent,
    Computer,
    Execution,
    ExecutionStep,
    Role,
    RolePermission,
    User,
    Workspace,
    WorkspaceMembership,
)
from sqlalchemy import delete, select


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


class FakePlanClient:
    async def create_plan(self, **_kwargs):
        return AIPlanResult(
            provider="fake",
            model="fake-model",
            provider_response_id="response-1",
            plan=AIPlan(
                summary="Read the current service status.",
                steps=[AIPlanStep(
                    step_id="status",
                    tool_name="system.status.read",
                    arguments={"services": ["core-api"]},
                    concise_rationale="Use the explicit health contract.",
                    depends_on=[],
                )],
                response_outline="Report the observed state without invention.",
            ),
            usage=AIUsage(input_tokens=11, output_tokens=7, total_tokens=18),
        )


class UnconfiguredPlanClient:
    async def create_plan(self, **_kwargs):
        raise AppError("PROVIDER_UNCONFIGURED", "The model provider is unconfigured.", 503)


class InvalidArgumentsPlanClient(FakePlanClient):
    async def create_plan(self, **_kwargs):
        result = await super().create_plan()
        result.plan.steps[0].arguments = {"services": ["secrets"]}
        return result


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


def activate_default_agent_with_verified_computer(client):
    async def activate():
        async with client.session_factory() as session:
            agent = await session.scalar(select(Agent))
            computer = await session.scalar(select(Computer).where(
                Computer.id == agent.computer_id,
                Computer.workspace_id == agent.workspace_id,
            ))
            computer.status = "running"
            computer.provider_resource_id = f"test-vm-{computer.id}"
            computer.compute_provider = "test"
            agent.status = "active"
            await session.commit()

    asyncio.run(activate())


def test_execution_plan_is_persisted_scoped_audited_and_cancellable(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    activate_default_agent_with_verified_computer(auth_client)
    app.dependency_overrides[get_ai_plan_client] = lambda: FakePlanClient()

    rejected = auth_client.post(
        "/api/v1/executions",
        json={"instruction": "Check whether the core service is online."},
    )
    assert rejected.status_code == 403
    assert rejected.json()["error"]["code"] == "CSRF_INVALID"

    created = auth_client.post("/api/v1/executions", json={
        "instruction": "Check whether the core service is online."
    }, headers=csrf_headers(auth_client))
    assert created.status_code == 201
    execution = created.json()["execution"]
    assert execution["status"] == "planned"
    assert execution["provider"] == "fake"
    assert execution["agent_id"]
    assert execution["input_tokens"] == 11
    assert execution["steps"][0]["tool_name"] == "system.status.read"
    assert execution["steps"][0]["access_type"] == "read"
    assert "arguments" not in execution["steps"][0]

    listed = auth_client.get("/api/v1/executions")
    assert listed.status_code == 200
    assert listed.json()["executions"][0]["id"] == execution["id"]
    detail = auth_client.get(f"/api/v1/executions/{execution['id']}")
    assert detail.status_code == 200
    assert detail.json()["execution"]["steps"][0]["status"] == "pending"

    csrf = auth_client.cookies.get("hayva_csrf")
    cancelled = auth_client.post(
        f"/api/v1/executions/{execution['id']}/cancel",
        headers={"x-csrf-token": csrf},
    )
    assert cancelled.status_code == 200
    assert cancelled.json()["execution"]["status"] == "cancelled"
    repeated = auth_client.post(
        f"/api/v1/executions/{execution['id']}/cancel",
        headers={"x-csrf-token": csrf},
    )
    assert repeated.status_code == 200


def test_unconfigured_provider_is_durable_blocked_state_not_simulated_success(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    activate_default_agent_with_verified_computer(auth_client)
    app.dependency_overrides[get_ai_plan_client] = lambda: UnconfiguredPlanClient()

    response = auth_client.post(
        "/api/v1/executions",
        json={"instruction": "Plan my day."},
        headers=csrf_headers(auth_client),
    )
    assert response.status_code == 503
    assert response.json()["error"]["code"] == "PROVIDER_UNCONFIGURED"

    async def read_execution():
        async with auth_client.session_factory() as session:
            return await session.scalar(select(Execution))

    record = asyncio.run(read_execution())
    assert record.status == "blocked"
    assert record.error_code == "PROVIDER_UNCONFIGURED"
    assert record.provider is None


def test_execution_detail_cannot_cross_workspace(auth_client):
    setup = auth_client.post("/api/v1/setup/owner", json=owner_payload()).json()
    other_execution_id = uuid.uuid4()

    async def create_other_workspace_execution():
        async with auth_client.session_factory() as session:
            user = await session.scalar(select(User))
            other_workspace = Workspace(id=uuid.uuid4(), name="Other", timezone="UTC")
            session.add(other_workspace)
            await session.flush()
            session.add(WorkspaceMembership(
                id=uuid.uuid4(), workspace_id=other_workspace.id, user_id=user.id, status="active"
            ))
            await session.flush()
            session.add(Execution(
                id=other_execution_id,
                workspace_id=other_workspace.id,
                requested_by_user_id=user.id,
                instruction="Private other-workspace instruction",
                instruction_hash="0" * 64,
                status="planned",
            ))
            await session.commit()

    asyncio.run(create_other_workspace_execution())
    assert setup["workspace_id"]
    response = auth_client.get(f"/api/v1/executions/{other_execution_id}")
    assert response.status_code == 404
    assert response.json()["error"]["code"] == "EXECUTION_NOT_FOUND"


def test_execution_runs_verified_read_tool_and_replay_is_idempotent(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    activate_default_agent_with_verified_computer(auth_client)
    app.dependency_overrides[get_ai_plan_client] = lambda: FakePlanClient()
    created = auth_client.post(
        "/api/v1/executions",
        json={"instruction": "Check whether the core service is online."},
        headers=csrf_headers(auth_client),
    ).json()["execution"]

    missing_csrf = auth_client.post(f"/api/v1/executions/{created['id']}/run")
    assert missing_csrf.status_code == 403
    executed = auth_client.post(
        f"/api/v1/executions/{created['id']}/run",
        headers=csrf_headers(auth_client),
    )
    assert executed.status_code == 200
    result = executed.json()["execution"]
    assert result["status"] == "succeeded"
    assert result["output_summary"] == "Completed 1 verified read step(s)."
    assert result["steps"][0]["status"] == "succeeded"
    assert result["steps"][0]["result"] == {"statuses": {"core-api": "ok"}}
    assert result["steps"][0]["verification"]["status"] == "verified"

    replay = auth_client.post(
        f"/api/v1/executions/{created['id']}/run",
        headers=csrf_headers(auth_client),
    )
    assert replay.status_code == 200
    assert replay.json()["execution"]["status"] == "succeeded"

    async def audit_types():
        async with auth_client.session_factory() as session:
            return list((await session.scalars(
                select(AuditEvent.event_type).order_by(AuditEvent.sequence)
            )).all())

    events = asyncio.run(audit_types())
    assert "execution.started" in events
    assert "execution.step_started" in events
    assert "execution.step_succeeded" in events
    assert "execution.succeeded" in events


def test_runtime_reauthorizes_tool_after_permission_revocation(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    activate_default_agent_with_verified_computer(auth_client)
    app.dependency_overrides[get_ai_plan_client] = lambda: FakePlanClient()
    created = auth_client.post(
        "/api/v1/executions",
        json={"instruction": "Check service status."},
        headers=csrf_headers(auth_client),
    ).json()["execution"]

    async def revoke_tool_permission():
        async with auth_client.session_factory() as session:
            owner_role_id = await session.scalar(select(Role.id).where(Role.name == "Owner"))
            await session.execute(delete(RolePermission).where(
                RolePermission.role_id == owner_role_id,
                RolePermission.permission_key == "system.status.read",
            ))
            await session.commit()

    asyncio.run(revoke_tool_permission())
    denied = auth_client.post(
        f"/api/v1/executions/{created['id']}/run",
        headers=csrf_headers(auth_client),
    )
    assert denied.status_code == 403
    assert denied.json()["error"]["code"] == "AI_PLAN_NOT_AUTHORIZED"

    async def read_states():
        async with auth_client.session_factory() as session:
            execution = await session.get(Execution, uuid.UUID(created["id"]))
            step = await session.scalar(select(ExecutionStep).where(
                ExecutionStep.execution_id == execution.id
            ))
            return execution.status, execution.error_code, step.status

    assert asyncio.run(read_states()) == (
        "failed", "AI_PLAN_NOT_AUTHORIZED", "failed"
    )


def test_core_rejects_invalid_tool_arguments_before_persistence(auth_client):
    auth_client.post("/api/v1/setup/owner", json=owner_payload())
    activate_default_agent_with_verified_computer(auth_client)
    app.dependency_overrides[get_ai_plan_client] = lambda: InvalidArgumentsPlanClient()
    response = auth_client.post(
        "/api/v1/executions",
        json={"instruction": "Read an invalid service."},
        headers=csrf_headers(auth_client),
    )
    assert response.status_code == 422
    assert response.json()["error"]["code"] == "TOOL_ARGUMENTS_INVALID"
