"""
Seat management endpoints for team collaboration.

Implements multi-user access control with tier-based seat limits.
"""

from datetime import datetime
from typing import Dict, List, Optional, Tuple
import logging

from fastapi import APIRouter, Depends, HTTPException, Header
from sqlalchemy import select, func, text
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, EmailStr

from ...models import (
    User,
    UserRole,
    UsageQuotaModel,
    Account,
    AdminAction,
    AccountMember,
    AccountEntitlements,
)
from ...db import get_session
from ...auth_core import get_current_identity
from ... import settings
from .auth_helpers import resolve_account_id

router = APIRouter(prefix="/api/v2/seats", tags=["seats"])
log = logging.getLogger(__name__)

# Tier-based seat limits (synced with stripe.ts SEAT_PRICING.LIMITS)
SEAT_LIMITS = {"free": 1, "pro": 5, "scale": 25, "unleashed": 50, "enterprise": 100}


async def _resolve_account_id(identity: dict, db: AsyncSession) -> str:
    account_id, _clerk_org_id = await resolve_account_id(identity, db)
    return account_id


# ============================================
# Request/Response Models
# ============================================


class InviteRequest(BaseModel):
    email: EmailStr
    role: UserRole = UserRole.VIEWER


class SeatResponse(BaseModel):
    id: str
    email: str
    role: str
    seat_status: str
    created_at: datetime
    last_login: Optional[datetime]
    invited_at: Optional[datetime]
    membership_status: Optional[str] = None
    membership_role: Optional[str] = None
    invited_by: Optional[str] = None
    accepted_at: Optional[datetime] = None


class SeatUsageResponse(BaseModel):
    active_seats: int
    max_seats: int
    tier: str
    can_add_seats: bool
    interval: Optional[str] = None  # "month" or "year"


class SyncStripeRequest(BaseModel):
    account_id: str
    total_seats: int
    stripe_subscription_id: Optional[str] = None
    tier: Optional[str] = None


class ActivateClerkUserRequest(BaseModel):
    org_id: str
    user_id: str  # Clerk user ID
    email: str


class DeactivateClerkUserRequest(BaseModel):
    org_id: str
    user_id: str  # Clerk user ID


# ============================================
# Helper Functions
# ============================================


async def _get_account_tier(account_id: str, db: AsyncSession) -> str:
    """Get current tier for account."""
    result = await db.execute(
        select(AccountEntitlements.tier).where(
            AccountEntitlements.account_id == account_id
        )
    )
    tier = result.scalar_one_or_none()
    if tier:
        return tier
    result = await db.execute(
        select(UsageQuotaModel.tier).where(UsageQuotaModel.account_id == account_id)
    )
    tier = result.scalar_one_or_none()
    return tier or "free"


async def _count_active_seats(account_id: str, db: AsyncSession) -> int:
    """Count active + invited seats for account."""
    result = await db.execute(
        select(func.count(User.id))
        .where(User.account_id == account_id)
        .where(User.seat_status.in_(["active", "invited"]))
    )
    return result.scalar() or 0


def _serialize_role(value: object) -> str:
    """Normalize stored role values to frontend-friendly strings."""
    resolved = UserRole.from_raw(value) or UserRole.VIEWER
    return UserRole.ADMIN.value if resolved == UserRole.OWNER else resolved.value


async def _get_account_members(
    db: AsyncSession, account_id: str
) -> Tuple[Dict[str, AccountMember], Dict[str, AccountMember]]:
    """Return lookup dictionaries for members by user_id and email."""
    result = await db.execute(
        select(AccountMember).where(AccountMember.account_id == account_id)
    )
    members = result.scalars().all()

    by_id: Dict[str, AccountMember] = {}
    by_email: Dict[str, AccountMember] = {}

    for member in members:
        key = member.user_id
        if key:
            by_id[key] = member
        if member.email:
            by_email[member.email.lower()] = member

    return by_id, by_email


async def _ensure_account_member(
    db: AsyncSession,
    account_id: str,
    user: User,
    *,
    status: Optional[str] = None,
    invited_by: Optional[str] = None,
    invited_at: Optional[datetime] = None,
    accepted: bool = False,
) -> AccountMember:
    """Create or update the account_members row corresponding to a user seat."""
    by_id, by_email = await _get_account_members(db, account_id)

    email_lower = (user.email or "").lower()
    possible_ids = []
    if user.clerk_user_id:
        possible_ids.append(user.clerk_user_id)
    possible_ids.append(f"pending:{user.id}")

    member: Optional[AccountMember] = None
    for candidate_id in possible_ids:
        if candidate_id and candidate_id in by_id:
            member = by_id[candidate_id]
            break

    if not member and email_lower and email_lower in by_email:
        member = by_email[email_lower]

    if not member:
        identifier = user.clerk_user_id or f"pending:{user.id}"
        member = AccountMember(
            account_id=account_id,
            user_id=identifier,
            email=user.email,
            role=_serialize_role(user.role),
            status=status or "active",
            invited_by=invited_by,
            invited_at=invited_at or user.invited_at,
            accepted_at=datetime.utcnow() if (status == "active" or accepted) else None,
        )
        db.add(member)
    else:
        if user.clerk_user_id and member.user_id != user.clerk_user_id:
            member.user_id = user.clerk_user_id
        if user.email and member.email != user.email:
            member.email = user.email
        if status:
            member.status = status
        if invited_by:
            member.invited_by = invited_by
        if invited_at and not member.invited_at:
            member.invited_at = invited_at
        if (status == "active" or accepted) and not member.accepted_at:
            member.accepted_at = datetime.utcnow()
        member.role = _serialize_role(user.role)

    if status == "active":
        member.last_active_at = datetime.utcnow()
    return member


async def _get_billing_interval(account_id: str, db: AsyncSession) -> Optional[str]:
    """Get billing interval (month/year) from active subscription."""
    from sqlalchemy import text

    # Primary: Query using accounts.stripe_subscription_id -> subscription -> plan
    query = text("""
        SELECT p.billing_interval
        FROM subscription s
        JOIN plan p ON s.plan_id = p.id
        JOIN accounts a ON a.stripe_subscription_id = s.stripe_subscription_id
        WHERE a.id = :account_id
        AND s.status IN ('active', 'trialing')
        LIMIT 1
    """)

    result = await db.execute(query, {"account_id": account_id})
    row = result.fetchone()

    if row and row[0]:
        return row[0]

    # Fallback: look up via subscription.account_id directly (skips accounts table join)
    # Cast UUID to text since account_id is a Clerk org_id string, not a UUID
    fallback_query = text("""
        SELECT p.billing_interval
        FROM subscription s
        JOIN plan p ON s.plan_id = p.id
        WHERE s.account_id::text = :account_id
        AND s.status IN ('active', 'trialing')
        ORDER BY s.updated_at DESC
        LIMIT 1
    """)

    result = await db.execute(fallback_query, {"account_id": account_id})
    row = result.fetchone()

    return row[0] if row else None


async def _verify_internal_api_key(x_internal_api_key: str = Header(None)) -> None:
    """Verify internal API key for webhook endpoints."""
    expected_key = getattr(settings, "INTERNAL_API_KEY", None)

    if not expected_key:
        # Fail closed in production. These endpoints mutate billing/seat state and must not
        # be callable by arbitrary authenticated users.
        if getattr(settings, "IS_PRODUCTION", False):
            log.error(
                "[SEATS] INTERNAL_API_KEY not configured - refusing internal webhook endpoint in production"
            )
            raise HTTPException(503, "Internal webhook auth not configured")
        log.warning(
            "[SEATS] INTERNAL_API_KEY not configured - allowing internal webhook endpoint in non-production"
        )
        return  # Allow in dev/testing

    # Use constant-time compare to avoid leaking key length/contents via timing.
    import hmac

    if not x_internal_api_key or not hmac.compare_digest(str(x_internal_api_key), str(expected_key)):
        raise HTTPException(403, "Invalid or missing internal API key")


# ============================================
# User-Facing Endpoints (Clerk JWT auth)
# ============================================


@router.get("/usage", response_model=SeatUsageResponse)
async def get_seat_usage(
    identity: dict = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    Get current seat usage for billing page display.

    Returns:
        active_seats: Number of active + invited seats
        max_seats: Maximum allowed seats for current tier
        tier: Current subscription tier
        can_add_seats: Whether more seats can be added
    """
    account_id = await _resolve_account_id(identity, db)

    acct_result = await db.execute(select(Account).where(Account.id == account_id))
    account = acct_result.scalar_one_or_none()
    paid_seats = max(getattr(account, "paid_seat_count", 1) or 1, 1)

    tier = await _get_account_tier(account_id, db)
    active_seats = await _count_active_seats(account_id, db)
    max_seats = paid_seats
    interval = await _get_billing_interval(account_id, db)

    log.info(
        f"[SEATS] Usage check: account={account_id} tier={tier} seats={active_seats}/{max_seats} interval={interval} paid={paid_seats}"
    )

    return SeatUsageResponse(
        active_seats=active_seats,
        max_seats=max_seats,
        tier=tier,
        can_add_seats=(active_seats < max_seats),
        interval=interval,
    )


@router.get("", response_model=List[SeatResponse])
async def list_seats(
    identity: dict = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    List all seats (active + invited) for current organization.

    Used by the team management UI to show all team members.
    """
    account_id = await _resolve_account_id(identity, db)

    result = await db.execute(
        select(User)
        .where(User.account_id == account_id)
        .where(User.seat_status.in_(["active", "invited"]))
        .order_by(User.created_at)
    )
    users = result.scalars().all()

    members_by_id, members_by_email = await _get_account_members(db, account_id)

    log.info(f"[SEATS] List seats: account={account_id} count={len(users)}")

    responses: List[SeatResponse] = []
    for user in users:
        member = None
        possible_ids = []
        if user.clerk_user_id:
            possible_ids.append(user.clerk_user_id)
        possible_ids.append(f"pending:{user.id}")

        for key in possible_ids:
            if key and key in members_by_id:
                member = members_by_id[key]
                break

        if not member:
            email_lower = (user.email or "").lower()
            if email_lower and email_lower in members_by_email:
                member = members_by_email[email_lower]

        responses.append(
            SeatResponse(
                id=user.id,
                email=user.email,
                role=_serialize_role(user.role),
                seat_status=user.seat_status,
                created_at=user.created_at,
                last_login=user.last_login,
                invited_at=(
                    member.invited_at
                    if member and member.invited_at
                    else user.invited_at
                ),
                membership_status=member.status if member else None,
                membership_role=member.role if member else None,
                invited_by=member.invited_by if member else None,
                accepted_at=member.accepted_at if member else None,
            )
        )

    return responses


@router.post("/invite")
async def invite_seat(
    invite: InviteRequest,
    identity: dict = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    Create a new seat invitation.

    Flow:
    1. Backend validates seat limit
    2. Backend creates "invited" user record
    3. Web team sends Clerk org invitation
    4. When user accepts, Clerk webhook calls /activate-clerk-user

    Returns:
        user_id: ID of created user (for web team to link to Clerk invite)
    """
    account_id = await _resolve_account_id(identity, db)
    user_email = identity.get("email")
    user_id = identity.get("user_id")

    # Check seat limit
    tier = await _get_account_tier(account_id, db)
    acct_result = await db.execute(select(Account).where(Account.id == account_id))
    account = acct_result.scalar_one_or_none()
    active_seats = await _count_active_seats(account_id, db)
    max_seats = getattr(account, "paid_seat_count", None) or SEAT_LIMITS.get(tier, 1)

    if active_seats >= max_seats:
        log.warning(
            f"[SEATS] Seat limit reached: account={account_id} "
            f"tier={tier} seats={active_seats}/{max_seats}"
        )
        raise HTTPException(
            403,
            f"Seat limit reached ({active_seats}/{max_seats}). Upgrade to add more users.",
        )

    # Check if user already exists
    result = await db.execute(select(User).where(User.email == invite.email))
    existing = result.scalar_one_or_none()

    if existing:
        if existing.account_id == account_id:
            raise HTTPException(
                400, f"User already in organization (status: {existing.seat_status})"
            )
        else:
            raise HTTPException(400, "User belongs to another organization")

    # Create invited user
    new_user = User(
        account_id=account_id,
        email=invite.email,
        role=invite.role,
        seat_status="invited",
        invited_at=datetime.utcnow(),
    )

    db.add(new_user)
    await db.flush()

    await _ensure_account_member(
        db,
        account_id=account_id,
        user=new_user,
        status="invited",
        invited_by=user_email,
        invited_at=new_user.invited_at,
    )

    # Log admin action
    admin_action = AdminAction(
        admin_user_id=user_id or "unknown",
        admin_email=user_email or "unknown",
        action="invite_seat",
        target_account_id=account_id,
        details={
            "invited_email": invite.email,
            "role": invite.role.value,
            "active_seats_before": active_seats,
            "active_seats_after": active_seats + 1,
        },
    )
    db.add(admin_action)
    await db.commit()
    await db.refresh(new_user)

    log.info(
        f"[SEATS] Seat invited: account={account_id} "
        f"email={invite.email} user_id={new_user.id} "
        f"seats={active_seats + 1}/{max_seats}"
    )

    return {
        "success": True,
        "user_id": new_user.id,
        "email": new_user.email,
        "message": "Seat created. Web team should now send Clerk invitation.",
    }


@router.delete("/{user_id}")
async def remove_seat(
    user_id: str,
    identity: dict = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    Remove a user's seat (soft delete).

    Sets seat_status to 'removed'. Web team should also:
    - Remove user from Clerk organization
    - Update Stripe subscription quantity
    """
    account_id = await _resolve_account_id(identity, db)
    admin_email = identity.get("email")
    admin_user_id = identity.get("user_id")

    # Get user to remove
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()

    if not user:
        raise HTTPException(404, "User not found")

    if user.account_id != account_id:
        raise HTTPException(403, "Cannot remove user from different organization")

    # Never allow removing the last active admin (even if removing someone else).
    if UserRole.is_admin_value(getattr(user, "role", None)) and user.seat_status == "active":
        admin_count_result = await db.execute(
            select(func.count(User.id))
            .where(User.account_id == account_id)
            .where(User.role.in_(UserRole.admin_roles()))
            .where(User.seat_status == "active")
        )
        admin_count = admin_count_result.scalar() or 0
        if admin_count <= 1:
            raise HTTPException(400, "Cannot remove the last admin")

    # Soft delete and sync membership
    user.seat_status = "removed"
    await _ensure_account_member(
        db,
        account_id=account_id,
        user=user,
        status="removed",
    )

    # Log action
    admin_action = AdminAction(
        admin_user_id=admin_user_id or "unknown",
        admin_email=admin_email or "unknown",
        action="remove_seat",
        target_account_id=account_id,
        details={"removed_user_email": user.email, "removed_user_id": user_id},
    )
    db.add(admin_action)
    await db.commit()

    log.info(
        f"[SEATS] Seat removed: account={account_id} "
        f"email={user.email} user_id={user_id} "
        f"by={admin_email}"
    )

    return {"success": True, "message": f"Seat removed: {user.email}"}


# ============================================
# Internal Endpoints (API key auth)
# ============================================


@router.post("/sync-stripe")
async def sync_stripe_seats(
    request: SyncStripeRequest,
    db: AsyncSession = Depends(get_session),
    _: None = Depends(_verify_internal_api_key),
):
    """
    Called by web team's Stripe webhook handler.

    Updates our record of how many seats customer is paying for.
    This helps with billing reconciliation and audit.
    """
    # Get current state before update
    result = await db.execute(select(Account).where(Account.id == request.account_id))
    account = result.scalar_one_or_none()

    if not account:
        log.error(f"[SEATS] Stripe sync failed: account {request.account_id} not found")
        raise HTTPException(404, f"Account {request.account_id} not found")

    # Get active seat count for comparison
    active_seats = await _count_active_seats(request.account_id, db)

    # Store previous values for audit logging
    old_paid_seats = account.paid_seat_count or 1
    old_subscription_id = account.stripe_subscription_id

    # Update account with paid seat count
    account.paid_seat_count = request.total_seats
    if request.stripe_subscription_id:
        account.stripe_subscription_id = request.stripe_subscription_id

    # Log admin action for audit trail
    admin_action = AdminAction(
        admin_user_id="stripe_webhook",
        admin_email="webhook@stripe.com",
        action="sync_seats_from_stripe",
        target_account_id=request.account_id,
        details={
            "old_paid_seats": old_paid_seats,
            "new_paid_seats": request.total_seats,
            "active_seats": active_seats,
            "tier": request.tier,
            "stripe_subscription_id": request.stripe_subscription_id,
            "previous_subscription_id": old_subscription_id,
            "seat_change": request.total_seats - old_paid_seats,
        },
    )
    db.add(admin_action)

    await db.commit()

    # Enhanced logging with before/after comparison
    log.info(
        f"[SEATS] Stripe sync complete: account={request.account_id} "
        f"tier={request.tier} "
        f"paid_seats: {old_paid_seats}→{request.total_seats} "
        f"active_seats={active_seats} "
        f"subscription={request.stripe_subscription_id}"
    )

    # Warning if active seats exceed paid seats
    if active_seats > request.total_seats:
        log.warning(
            f"[SEATS] OVERAGE DETECTED: account={request.account_id} "
            f"has {active_seats} active seats but only paying for {request.total_seats}"
        )

    return {
        "success": True,
        "account_id": request.account_id,
        "paid_seat_count": request.total_seats,
        "active_seats": active_seats,
        "previous_paid_seats": old_paid_seats,
        "overage": active_seats > request.total_seats,
    }


@router.post("/activate-clerk-user")
async def activate_clerk_user(
    request: ActivateClerkUserRequest,
    db: AsyncSession = Depends(get_session),
    _: None = Depends(_verify_internal_api_key),
):
    """
    Called by web team's Clerk webhook when user accepts org invitation.

    Activates the invited seat by:
    1. Finding the "invited" user by email
    2. Setting seat_status to "active"
    3. Linking to Clerk user_id
    """
    account_id, _clerk_org_id = await resolve_account_id(
        {"org_id": request.org_id}, db
    )

    # Find invited user by email and org_id
    result = await db.execute(
        select(User)
        .where(User.email == request.email)
        .where(User.account_id == account_id)
    )
    user = result.scalar_one_or_none()

    if not user:
        # User might not have been invited through our system
        # Create them now (e.g., if org owner added them directly in Clerk)
        log.warning(
            f"[SEATS] User not found during activation, creating: "
            f"org={request.org_id} email={request.email}"
        )
        user = User(
            account_id=account_id,
            email=request.email,
            role=UserRole.VIEWER,
            seat_status="active",
            clerk_user_id=request.user_id,
        )
        db.add(user)
    else:
        # Activate invited user
        user.seat_status = "active"
        user.clerk_user_id = request.user_id
        user.last_login = datetime.utcnow()

    await db.flush()

    await _ensure_account_member(
        db,
        account_id=account_id,
        user=user,
        status="active",
        invited_at=user.invited_at,
        accepted=True,
    )

    await db.commit()
    await db.refresh(user)

    log.info(
        f"[SEATS] Clerk user activated: org={request.org_id} "
        f"email={request.email} clerk_user_id={request.user_id} "
        f"user_id={user.id}"
    )

    return {
        "success": True,
        "user_id": user.id,
        "email": user.email,
        "seat_status": user.seat_status,
    }


@router.post("/deactivate-clerk-user")
async def deactivate_clerk_user(
    request: DeactivateClerkUserRequest,
    db: AsyncSession = Depends(get_session),
    _: None = Depends(_verify_internal_api_key),
):
    """
    Called when user is removed from Clerk org.

    Suspends the user's seat (sets status to "suspended").
    """
    account_id, _clerk_org_id = await resolve_account_id(
        {"org_id": request.org_id}, db
    )

    # Find user by clerk_user_id
    result = await db.execute(select(User).where(User.clerk_user_id == request.user_id))
    user = result.scalar_one_or_none()

    if not user:
        log.warning(
            f"[SEATS] User not found during deactivation: "
            f"clerk_user_id={request.user_id}"
        )
        return {"success": True, "message": "User not found (already removed?)"}

    if user.account_id != account_id:
        raise HTTPException(403, "Cannot deactivate user from different organization")

    user.seat_status = "suspended"
    await db.flush()

    await _ensure_account_member(
        db,
        account_id=account_id,
        user=user,
        status="suspended",
    )

    await db.commit()

    log.info(
        f"[SEATS] Clerk user deactivated: org={request.org_id} "
        f"email={user.email} clerk_user_id={request.user_id}"
    )

    return {
        "success": True,
        "user_id": user.id,
        "email": user.email,
        "seat_status": "suspended",
    }
