"""Durable grants and hashed OAuth credentials. No browser/session tokens stored."""

from datetime import datetime
from uuid import UUID, uuid4

from sqlalchemy import JSON, DateTime, ForeignKey, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column

from src.core.database import Base


class AgentClient(Base):
    __tablename__ = "agent_clients"
    id: Mapped[str] = mapped_column(String(64), primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    redirect_uris: Mapped[list] = mapped_column(JSON)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))


class AgentGrant(Base):
    __tablename__ = "agent_grants"
    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
    workspace_id: Mapped[UUID] = mapped_column(ForeignKey("workspaces.id"), index=True)
    created_by: Mapped[UUID] = mapped_column(ForeignKey("users.id"))
    client_id: Mapped[str | None] = mapped_column(ForeignKey("agent_clients.id"))
    name: Mapped[str] = mapped_column(String(100))
    kind: Mapped[str] = mapped_column(String(16))
    scopes: Mapped[list] = mapped_column(JSON)
    project_ids: Mapped[list] = mapped_column(JSON)
    workspace_wide: Mapped[bool]
    purchase_policy: Mapped[dict] = mapped_column(JSON)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    revoked_by: Mapped[UUID | None] = mapped_column(ForeignKey("users.id"))
    revocation_reason: Mapped[str | None] = mapped_column(String(40))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))


class AgentGrantWorkspace(Base):
    """One workspace a grant may act in, with that workspace's own delegated scopes
    and spending policy. This is the source of truth for per-workspace authority;
    ``AgentGrant.scopes`` is the union (used for OAuth scope negotiation only) and
    ``AgentGrant.workspace_id`` is just the first workspace, kept for back-compat.
    ``project_ids``/``workspace_wide`` only restrict rows migrated from legacy
    single-project grants; new grants always cover every project in the workspace.
    """

    __tablename__ = "agent_grant_workspaces"
    grant_id: Mapped[UUID] = mapped_column(
        ForeignKey("agent_grants.id", ondelete="CASCADE"), primary_key=True
    )
    workspace_id: Mapped[UUID] = mapped_column(
        ForeignKey("workspaces.id"), primary_key=True, index=True
    )
    scopes: Mapped[list] = mapped_column(JSON)
    project_ids: Mapped[list] = mapped_column(JSON)
    workspace_wide: Mapped[bool]
    purchase_policy: Mapped[dict] = mapped_column(JSON)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))


class WorkspaceBillingDelegation(Base):
    """An owner's decision to let one member put billing scopes on agent connections.

    Roles give a member exactly one role and none combines developer access (needed to
    share a workspace with an agent) with billing.manage, so a non-owner could never
    delegate billing. This is the owner-approved exception, deliberately outside the role
    model: it never changes the member's role or permissions anywhere else, applies only to
    the delegable billing scopes (see schemas.DELEGABLE_BILLING_SCOPES; never
    credits.purchase, which stays owner-only), and is rechecked on every agent call, so
    revoking it (or the approving owner losing ownership) stops it at once.

    One row per (workspace, member); a new request reuses the row.
    status: pending | approved | declined | revoked
    """

    __tablename__ = "workspace_billing_delegations"
    __table_args__ = (
        UniqueConstraint("workspace_id", "member_id", name="uq_billing_delegation_member"),
    )
    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
    workspace_id: Mapped[UUID] = mapped_column(ForeignKey("workspaces.id"), index=True)
    member_id: Mapped[UUID] = mapped_column(ForeignKey("users.id"), index=True)
    status: Mapped[str] = mapped_column(String(16))
    requested_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    resolved_by: Mapped[UUID | None] = mapped_column(ForeignKey("users.id"))


class AgentCredential(Base):
    __tablename__ = "agent_credentials"
    fingerprint: Mapped[str] = mapped_column(String(64), primary_key=True)
    grant_id: Mapped[UUID] = mapped_column(ForeignKey("agent_grants.id"), index=True)
    kind: Mapped[str] = mapped_column(String(16))
    client_id: Mapped[str] = mapped_column(String(64))
    details: Mapped[dict] = mapped_column(JSON)
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))


class AgentOperation(Base):
    """Durable idempotency record/outbox for billable or asynchronous tool actions."""

    __tablename__ = "agent_operations"
    __table_args__ = (UniqueConstraint("grant_id", "request_key", name="uq_agent_operation_key"),)
    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
    grant_id: Mapped[UUID] = mapped_column(ForeignKey("agent_grants.id"), index=True)
    workspace_id: Mapped[UUID] = mapped_column(ForeignKey("workspaces.id"), index=True)
    request_key: Mapped[str] = mapped_column(String(128))
    action: Mapped[str] = mapped_column(String(40))
    request_hash: Mapped[str] = mapped_column(String(64))
    arguments: Mapped[dict] = mapped_column(JSON)
    # Which interaction surface initiated this: "mcp_agent" | "a2a_agent" | "api" | None
    # (legacy rows / dashboard-originated operations predate this column). The
    # initiating grant is already `grant_id` above — this only answers *how* it arrived.
    prompt_source: Mapped[str | None] = mapped_column(String(20), nullable=True)
    status: Mapped[str] = mapped_column(String(24), index=True)
    result: Mapped[dict] = mapped_column(JSON)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
