"""In-memory token lifecycle management for enterprise platform integration.

SECURITY: Tokens are NEVER written to disk. They exist only in memory
for the duration of the active session. This module manages token refresh,
expiry checking, and secure cleanup.
"""

from __future__ import annotations

import logging
import time
from datetime import datetime, timezone
from typing import Any

from pydantic import BaseModel, Field

logger = logging.getLogger(__name__)


class TokenEntry(BaseModel):
    """A single token stored in memory."""
    token_type: str = Field(description="Token type identifier (e.g., 'platform_jwt', 'ado_jwt', 'github_pat')")
    value: str = Field(description="The token value (NEVER log this)")
    issued_at: float = Field(default_factory=time.time, description="Unix timestamp when token was stored")
    expires_at: float | None = Field(default=None, description="Unix timestamp when token expires")
    scopes: list[str] = Field(default_factory=list, description="Token scopes/permissions")
    metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata (no secrets)")

    @property
    def is_expired(self) -> bool:
        """Check if the token has expired (with 60s buffer)."""
        if self.expires_at is None:
            return False
        return time.time() >= (self.expires_at - 60)  # 60s buffer for safety

    @property
    def remaining_seconds(self) -> float | None:
        """Seconds until expiry, or None if no expiry."""
        if self.expires_at is None:
            return None
        return max(0.0, self.expires_at - time.time())

    def __repr__(self) -> str:
        """Safe repr that never shows the token value."""
        return (
            f"TokenEntry(type={self.token_type!r}, "
            f"expired={self.is_expired}, "
            f"remaining={self.remaining_seconds})"
        )


class TokenManager:
    """In-memory token manager with zero disk persistence.
    
    Manages tokens for enterprise platform connections. All tokens are held
    in memory only and are destroyed when the manager is garbage collected
    or explicitly cleared.
    
    Usage:
        mgr = TokenManager()
        mgr.store("platform_jwt", token_value, expires_at=1771230588)
        token = mgr.get("platform_jwt")
        if mgr.is_valid("platform_jwt"):
            ...
        mgr.clear_all()  # Explicit cleanup
    """

    def __init__(self) -> None:
        self._tokens: dict[str, TokenEntry] = {}
        logger.debug("TokenManager initialized (in-memory only, zero persistence)")

    def store(
        self,
        token_type: str,
        value: str,
        expires_at: float | None = None,
        scopes: list[str] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """Store a token in memory.
        
        Args:
            token_type: Identifier for the token (e.g., 'platform_jwt').
            value: The actual token string. NEVER logged.
            expires_at: Optional Unix timestamp for expiry.
            scopes: Optional list of token scopes.
            metadata: Optional metadata (must NOT contain secrets).
        """
        entry = TokenEntry(
            token_type=token_type,
            value=value,
            expires_at=expires_at,
            scopes=scopes or [],
            metadata=metadata or {},
        )
        self._tokens[token_type] = entry
        logger.info(
            "Token stored: type=%s, expires_in=%s",
            token_type,
            f"{entry.remaining_seconds:.0f}s" if entry.remaining_seconds else "never",
        )

    def get(self, token_type: str) -> str | None:
        """Get a token value if it exists and is not expired.
        
        Returns None if the token doesn't exist or has expired.
        Expired tokens are automatically removed.
        """
        entry = self._tokens.get(token_type)
        if entry is None:
            return None
        
        if entry.is_expired:
            logger.warning("Token expired: type=%s, removing from memory", token_type)
            self.remove(token_type)
            return None
        
        return entry.value

    def get_entry(self, token_type: str) -> TokenEntry | None:
        """Get the full token entry (for metadata inspection)."""
        return self._tokens.get(token_type)

    def is_valid(self, token_type: str) -> bool:
        """Check if a token exists and is not expired."""
        return self.get(token_type) is not None

    def remove(self, token_type: str) -> None:
        """Remove a token from memory."""
        if token_type in self._tokens:
            # Overwrite the value before removing (defense in depth)
            self._tokens[token_type].value = ""
            del self._tokens[token_type]
            logger.info("Token removed: type=%s", token_type)

    def clear_all(self) -> None:
        """Remove all tokens from memory."""
        for token_type in list(self._tokens.keys()):
            self.remove(token_type)
        logger.info("All tokens cleared from memory")

    def list_types(self) -> list[str]:
        """List all stored token types (not values)."""
        return list(self._tokens.keys())

    def summary(self) -> dict[str, Any]:
        """Get a safe summary of token state (no secret values)."""
        return {
            token_type: {
                "is_valid": not entry.is_expired,
                "remaining_seconds": entry.remaining_seconds,
                "scopes": entry.scopes,
                "metadata": entry.metadata,
            }
            for token_type, entry in self._tokens.items()
        }

    def __del__(self) -> None:
        """Ensure tokens are wiped on garbage collection."""
        try:
            self.clear_all()
        except Exception:
            pass  # Suppress errors during cleanup

    def __len__(self) -> int:
        return len(self._tokens)

    def __contains__(self, token_type: str) -> bool:
        return self.is_valid(token_type)
