"""
Tenant context for strict multi-tenant isolation.

Provides an immutable, validated TenantContext that is threaded through
the entire AiCippy system — memory, WebSocket, orchestrator, session —
to ensure zero cross-tenant data leakage.

Every data path (file storage, WebSocket, Bedrock calls, session) is
scoped to ``{tenant_id}/{user_id}`` via ``TenantContext.storage_namespace``.
"""

from __future__ import annotations

import re
import warnings
from dataclasses import dataclass
from typing import Final

_SAFE_ID_PATTERN: Final[re.Pattern[str]] = re.compile(r"^[a-zA-Z0-9_@.\-]+$")


def _validate_safe_id(value: str, field_name: str) -> None:
    """Validate an identifier is safe for use in file paths and URLs.

    Rejects empty values, path traversal sequences (``..``, ``/``, ``\\``),
    and any characters outside the safe set ``[a-zA-Z0-9_@.-]``.

    Args:
        value: The identifier to validate.
        field_name: Name of the field (for error messages).

    Raises:
        ValueError: If the value contains unsafe characters or patterns.
    """
    if not value:
        msg = f"Invalid {field_name}: must not be empty"
        raise ValueError(msg)
    if ".." in value or "/" in value or "\\" in value:
        msg = f"Invalid {field_name}: path traversal characters detected"
        raise ValueError(msg)
    if not _SAFE_ID_PATTERN.match(value):
        msg = f"Invalid {field_name}: contains unsafe characters (allowed: a-zA-Z0-9_@.-)"
        raise ValueError(msg)


@dataclass(frozen=True, slots=True)
class TenantContext:
    """Immutable, validated tenant context — single source of truth.

    Created once after login from ``PlanInfo`` and Cognito user data,
    then threaded to every component that handles tenant-scoped data.

    Attributes:
        tenant_id: AiVibe tenant identifier (e.g. ``vk_abc123``).
        user_id: Cognito ``sub`` or email-based identifier.
        email: User email address.
        org_tenant_id: Organization tenant ID (empty if not in an org).
    """

    tenant_id: str
    user_id: str
    email: str
    org_tenant_id: str = ""

    def __post_init__(self) -> None:
        """Validate all identifiers on construction."""
        _validate_safe_id(self.tenant_id, "tenant_id")
        _validate_safe_id(self.user_id, "user_id")

    @property
    def storage_namespace(self) -> str:
        """Return the file-system namespace for this tenant+user.

        Used by memory and history classes to isolate storage:
        ``~/.aicippy/conversations/{tenant_id}/{user_id}/``
        """
        return f"{self.tenant_id}/{self.user_id}"

    @staticmethod
    def _warn_deprecated_user_id() -> None:
        """Emit a deprecation warning for bare user_id usage."""
        warnings.warn(
            "Passing bare user_id without TenantContext is deprecated. "
            "Pass a TenantContext instance for proper tenant isolation.",
            DeprecationWarning,
            stacklevel=4,
        )
