"""
Sharing API for DocuVault — Phase 4, Plan 04-04.

Implements SHARE-01 through SHARE-05:
  POST   /api/shares             — grant share by recipient handle
  GET    /api/shares             — list shares owned by current user for a document
  GET    /api/shares/received    — virtual "Shared with me" folder (metadata only)
  DELETE /api/shares/{share_id}  — revoke share with IDOR protection

Security invariants:
  T-04-04-02: DELETE asserts share.owner_id == current_user.id → 404 on mismatch
  T-04-04-03: GET /received returns metadata only — extracted_text is never included
  T-04-04-04: No quota table is touched anywhere in this module
  T-04-04-05: UniqueConstraint(document_id, recipient_id) → IntegrityError → 409
"""
from __future__ import annotations

import uuid
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from pydantic import BaseModel, field_validator
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from db.models import Document, Share, User
from deps.auth import get_regular_user
from deps.db import get_db
from deps.utils import get_client_ip
from services.audit import write_audit_log

router = APIRouter(prefix="/api/shares", tags=["shares"])


# ── Request models ────────────────────────────────────────────────────────────


class ShareCreate(BaseModel):
    document_id: str
    recipient_handle: str
    permission: str = "view"

    @field_validator("permission")
    @classmethod
    def validate_permission(cls, v: str) -> str:
        if v not in {"view", "edit"}:
            raise ValueError("permission must be 'view' or 'edit'")
        return v


class SharePermissionPatch(BaseModel):
    permission: str

    @field_validator("permission")
    @classmethod
    def validate_permission(cls, v: str) -> str:
        if v not in {"view", "edit"}:
            raise ValueError("permission must be 'view' or 'edit'")
        return v


# ── Helpers ───────────────────────────────────────────────────────────────────



# ── POST /api/shares ──────────────────────────────────────────────────────────


@router.post("", status_code=status.HTTP_201_CREATED)
async def grant_share(
    body: ShareCreate,
    request: Request,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
) -> dict:
    """Grant document share to a user identified by their handle (SHARE-01, D-04).

    T-04-04-06: Only document owner can grant; 404 prevents ID enumeration.
    T-04-04-01: get_regular_user ensures admins cannot invoke this endpoint.
    T-04-04-05: Duplicate share → IntegrityError → 409 (no unbounded inserts).
    """
    # Parse document_id as UUID (T-03-11 pattern)
    try:
        uid = uuid.UUID(body.document_id)
    except ValueError:
        raise HTTPException(status_code=404, detail="Document not found")

    # Ownership assertion — 404 prevents ID enumeration
    doc = await session.get(Document, uid)
    if doc is None or doc.user_id != current_user.id:
        raise HTTPException(status_code=404, detail="Document not found")

    # Recipient lookup by exact handle (D-04)
    result = await session.execute(
        select(User).where(User.handle == body.recipient_handle)
    )
    recipient = result.scalar_one_or_none()
    if recipient is None:
        raise HTTPException(status_code=404, detail="User not found")

    # Self-share prevention
    if recipient.id == current_user.id:
        raise HTTPException(status_code=400, detail="Cannot share with yourself")

    # Create the share row
    share = Share(
        document_id=uid,
        owner_id=current_user.id,
        recipient_id=recipient.id,
        permission=body.permission,
    )
    session.add(share)

    try:
        await session.flush()
    except IntegrityError:
        await session.rollback()
        raise HTTPException(
            status_code=409, detail="Document already shared with this user"
        )

    # Audit log — write within same transaction (D-14)
    await write_audit_log(
        session=session,
        event_type="share.granted",
        user_id=current_user.id,
        actor_id=current_user.id,
        resource_id=uid,
        ip_address=get_client_ip(request),
        metadata_={"recipient_id": str(recipient.id)},
    )

    await session.commit()

    return {
        "id": str(share.id),
        "document_id": str(share.document_id),
        "owner_id": str(share.owner_id),
        "recipient_id": str(share.recipient_id),
        "recipient_handle": recipient.handle,
        "permission": share.permission,
        "created_at": share.created_at.isoformat() if share.created_at else None,
    }


# ── GET /api/shares ───────────────────────────────────────────────────────────


@router.get("")
async def list_shares(
    document_id: str = Query(...),
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
) -> dict:
    """List shares owned by current user for a specific document (SHARE-01, D-05).

    Only the document owner can list shares — 404 on mismatch or bad UUID.
    """
    try:
        uid = uuid.UUID(document_id)
    except ValueError:
        raise HTTPException(status_code=404, detail="Document not found")

    doc = await session.get(Document, uid)
    if doc is None or doc.user_id != current_user.id:
        raise HTTPException(status_code=404, detail="Document not found")

    # Join Share with User to get recipient handles
    stmt = (
        select(Share, User)
        .join(User, User.id == Share.recipient_id)
        .where(Share.document_id == uid)
    )
    result = await session.execute(stmt)
    rows = result.all()

    items = []
    for share, recipient in rows:
        items.append(
            {
                "id": str(share.id),
                "recipient_id": str(share.recipient_id),
                "recipient_handle": recipient.handle,
                "permission": share.permission,
                "created_at": share.created_at.isoformat()
                if share.created_at
                else None,
            }
        )

    return {"items": items}


# ── GET /api/shares/received ──────────────────────────────────────────────────
# CRITICAL: This endpoint MUST be defined BEFORE DELETE /api/shares/{share_id}.
# Defining it after would cause FastAPI to route GET /api/shares/received as
# DELETE with share_id="received" (path parameter conflict).


@router.get("/received")
async def list_shared_with_me(
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
) -> dict:
    """Return documents shared WITH the current user (virtual "Shared with me" folder — D-06).

    T-04-04-03: Only metadata is returned — extracted_text is never included.
    T-04-04-04: No quota is modified.
    Response: {items: [{id, filename, content_type, size_bytes, created_at, owner_handle}]}
    """
    stmt = (
        select(Document, User)
        .join(Share, Share.document_id == Document.id)
        .join(User, User.id == Share.owner_id)
        .where(Share.recipient_id == current_user.id)
        .order_by(Document.created_at.desc())
    )
    result = await session.execute(stmt)
    rows = result.all()

    items = []
    for doc, owner in rows:
        # T-04-04-03: extracted_text is intentionally excluded here
        items.append(
            {
                "id": str(doc.id),
                "filename": doc.filename,
                "content_type": doc.content_type,
                "size_bytes": doc.size_bytes,
                "created_at": doc.created_at.isoformat() if doc.created_at else None,
                "owner_handle": owner.handle,
            }
        )

    return {"items": items}


# ── PATCH /api/shares/{share_id} ─────────────────────────────────────────────


@router.patch("/{share_id}", status_code=200)
async def update_share_permission(
    share_id: str,
    body: SharePermissionPatch,
    request: Request,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
) -> dict:
    """Update the permission on an existing share (SHARE-03, D-09).

    T-06.2-02-01 IDOR protection: 404 on owner mismatch — mirrors revoke_share exactly.
    T-06.2-02-02: SharePermissionPatch validator prevents arbitrary string passthrough.
    """
    try:
        sid = uuid.UUID(share_id)
    except ValueError:
        raise HTTPException(status_code=404, detail="Share not found")

    share = await session.get(Share, sid)
    if share is None or share.owner_id != current_user.id:
        raise HTTPException(status_code=404, detail="Share not found")

    share.permission = body.permission

    await write_audit_log(
        session=session,
        event_type="share.permission_changed",
        user_id=current_user.id,
        actor_id=current_user.id,
        resource_id=share.document_id,
        ip_address=get_client_ip(request),
        metadata_={"share_id": str(share.id), "new_permission": body.permission},
    )
    await session.commit()

    return {"id": str(share.id), "permission": share.permission}


# ── DELETE /api/shares/{share_id} ─────────────────────────────────────────────


@router.delete("/{share_id}", status_code=status.HTTP_204_NO_CONTENT)
async def revoke_share(
    share_id: str,
    request: Request,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
) -> None:
    """Revoke a share. Only the share owner may revoke (SHARE-04, D-07).

    T-04-04-02 IDOR protection: asserts share.owner_id == current_user.id.
    Returns 404 (not 403) on mismatch to prevent share ID enumeration.
    """
    try:
        sid = uuid.UUID(share_id)
    except ValueError:
        raise HTTPException(status_code=404, detail="Share not found")

    share = await session.get(Share, sid)
    # CRITICAL IDOR check: 404 on mismatch (not 403) — prevents ID enumeration
    if share is None or share.owner_id != current_user.id:
        raise HTTPException(status_code=404, detail="Share not found")

    document_id = share.document_id
    recipient_id = share.recipient_id

    await session.delete(share)

    # Audit log before commit (D-14 — within the same transaction)
    await write_audit_log(
        session=session,
        event_type="share.revoked",
        user_id=current_user.id,
        actor_id=current_user.id,
        resource_id=document_id,
        ip_address=get_client_ip(request),
        metadata_={"recipient_id": str(recipient_id)},
    )

    await session.commit()
