import secrets
from datetime import datetime, timezone
from typing import Annotated, Optional
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Request, status, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.orm import joinedload
from sqlalchemy.exc import IntegrityError

from app.core.database import get_db
from app.models.session import Session
from app.models.session_share import SessionShare, SessionShareView
from app.models.user import User
from app.models.account import Account
from app.schemas.session_share import ShareCreate, ShareResponse, SharePublicView
from app.api.deps import get_current_active_user, require_engineer_or_admin
from app.core.audit import log_audit
from app.core.rate_limit import limiter

router = APIRouter(tags=["shares"])


def build_share_response(share: SessionShare) -> ShareResponse:
    return ShareResponse(
        id=share.id,
        session_id=share.session_id,
        account_id=share.account_id,
        share_token=share.share_token,
        share_name=share.share_name,
        visibility=share.visibility,
        created_by=share.created_by,
        created_at=share.created_at,
        updated_at=share.updated_at,
        expires_at=share.expires_at,
        view_count=share.view_count,
        last_viewed_at=share.last_viewed_at,
        is_active=share.is_active,
    )


# --- Session Share CRUD ---


@router.post(
    "/sessions/{session_id}/shares",
    response_model=ShareResponse,
    status_code=status.HTTP_201_CREATED
)
async def create_share(
    session_id: UUID,
    share_data: ShareCreate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(require_engineer_or_admin)]
):
    """Create a share link for a session.

    Only the session owner can create shares.
    Public shares require account.allow_public_shares policy.
    """
    # Verify session exists and user owns it
    result = await db.execute(
        select(Session).where(Session.id == session_id)
    )
    session = result.scalar_one_or_none()

    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Session not found"
        )

    if session.user_id != current_user.id and not current_user.is_super_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Only the session owner can create share links"
        )

    # Require account_id for account-scoped shares
    if share_data.visibility == "account" and not current_user.account_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot create account-scoped share without an account"
        )

    # Check account policy for public shares
    if share_data.visibility == "public" and current_user.account_id:
        account_result = await db.execute(
            select(Account).where(Account.id == current_user.account_id)
        )
        account = account_result.scalar_one_or_none()
        if account and not account.allow_public_shares:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Your organization does not allow public session sharing. Use account-only visibility."
            )

    # Generate token with collision retry
    max_retries = 3
    for attempt in range(max_retries):
        try:
            share_token = secrets.token_urlsafe(48)

            share = SessionShare(
                session_id=session_id,
                account_id=current_user.account_id,
                share_token=share_token,
                share_name=share_data.share_name,
                visibility=share_data.visibility,
                created_by=current_user.id,
                expires_at=share_data.expires_at,
            )

            db.add(share)
            await db.flush()

            await log_audit(db, current_user.id, "share.create", "session_share", share.id,
                            {"session_id": str(session_id), "visibility": share_data.visibility})
            await db.commit()
            await db.refresh(share)

            return build_share_response(share)

        except IntegrityError as e:
            await db.rollback()
            if "session_shares_share_token_key" in str(e) and attempt < max_retries - 1:
                continue
            raise


@router.get("/shares/my-shares", response_model=list[ShareResponse])
async def list_my_shares(
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)],
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=100)
):
    """List all shares created by the current user."""
    result = await db.execute(
        select(SessionShare)
        .where(
            SessionShare.created_by == current_user.id,
            SessionShare.is_active == True
        )
        .order_by(SessionShare.created_at.desc())
        .offset(skip)
        .limit(limit)
    )
    shares = result.scalars().all()
    return [build_share_response(s) for s in shares]


@router.delete("/shares/{share_id}", status_code=status.HTTP_204_NO_CONTENT)
async def revoke_share(
    share_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_active_user)]
):
    """Revoke a share link (soft delete - sets is_active=False)."""
    result = await db.execute(
        select(SessionShare).where(SessionShare.id == share_id)
    )
    share = result.scalar_one_or_none()

    if not share:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Share not found"
        )

    if share.created_by != current_user.id and not current_user.is_super_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Only the share creator can revoke it"
        )

    share.is_active = False

    await log_audit(db, current_user.id, "share.revoke", "session_share", share.id,
                    {"session_id": str(share.session_id)})
    await db.commit()
    return None


# --- Public Share Access ---


async def _get_optional_user(request: Request, db: AsyncSession) -> Optional[User]:
    """Try to extract authenticated user from request, return None if not authenticated."""
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return None
    token = auth_header.replace("Bearer ", "")
    try:
        from app.core.security import decode_token
        payload = decode_token(token)
        if not payload or payload.get("type") != "access":
            return None
        user_id = payload.get("sub")
        if not user_id:
            return None
        result = await db.execute(select(User).where(User.id == UUID(user_id)))
        return result.scalar_one_or_none()
    except Exception:
        return None


@router.get("/share/{share_token}", response_model=SharePublicView)
@limiter.limit("30/minute")
async def access_share(
    share_token: str,
    request: Request,
    db: Annotated[AsyncSession, Depends(get_db)],
):
    """Access a shared session via share token.

    Public shares: No authentication required.
    Account-only shares: Requires authentication + account membership.
    """
    current_user = await _get_optional_user(request, db)

    # Lookup share
    result = await db.execute(
        select(SessionShare)
        .options(joinedload(SessionShare.session))
        .where(SessionShare.share_token == share_token)
    )
    share = result.scalar_one_or_none()

    # Validate share
    if not share or not share.is_active:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Share not found or has been revoked"
        )

    if share.expires_at and share.expires_at < datetime.now(timezone.utc):
        raise HTTPException(
            status_code=status.HTTP_410_GONE,
            detail="Share link has expired"
        )

    # Check visibility
    if share.visibility == "account":
        if not current_user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="This share requires authentication"
            )
        if current_user.account_id != share.account_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="You don't have access to this session"
            )

    # Record view
    session = share.session
    view = SessionShareView(
        share_id=share.id,
        session_id=session.id,
        viewer_id=current_user.id if current_user else None,
        viewer_ip=request.client.host if request.client else None,
        viewer_user_agent=request.headers.get("user-agent"),
    )
    db.add(view)

    share.view_count += 1
    share.last_viewed_at = datetime.now(timezone.utc)
    await db.commit()

    # Build read-only response
    tree_snapshot = session.tree_snapshot or {}
    return SharePublicView(
        session_id=session.id,
        tree_name=tree_snapshot.get("question", "Untitled Tree"),
        tree_description=tree_snapshot.get("description"),
        tree_structure=tree_snapshot,
        path_taken=session.path_taken or [],
        decisions=session.decisions or [],
        custom_steps=session.custom_steps or [],
        started_at=session.started_at,
        completed_at=session.completed_at,
        ticket_number=session.ticket_number,
        client_name=session.client_name,
        share_name=share.share_name,
        visibility=share.visibility,
    )
