"""
SaaS Secret Storage Backend for BYO Provider Locker.

Implements server-side credential storage for Unleashed tier workspaces.
Uses KMS envelope encryption for secrets stored in PostgreSQL.

This is an async-first backend for use in FastAPI endpoints and Cloud Tasks workers.
"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, List, Optional, TYPE_CHECKING
from uuid import UUID

import logging

logger = logging.getLogger(__name__)
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession

from g_gremlin.credentials.models import SecretSource, SecretValue

from ..models import WorkspaceSecret, SecretAuditLog, SecretAuditAction
from .kms_envelope import (
    EncryptedSecret,
    KMSEnvelopeEncryption,
    get_envelope_encryption,
    get_key_suffix,
)

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession


# Supported integrations for BYO Provider Locker (V1)
# FoundryGraph is always available (internal), so not in this list
BYO_SUPPORTED_INTEGRATIONS = frozenset(
    [
        "fullenrich",
        "apify",
        "nimbleway",
        "apollo",
        "amplemarket",
        "smartlead",
        "instantly",
        "pdl",
        "justcall",
        "heyreach",
    ]
)

# OAuth-managed integrations that also use envelope-encrypted workspace secrets.
OAUTH_SUPPORTED_INTEGRATIONS = frozenset(["hubspot", "outreach", "zoominfo"])
LOCKER_OAUTH_ONLY_INTEGRATIONS = frozenset(["apollo", "outreach"])
LOCKER_SUPPORTED_INTEGRATIONS = frozenset(
    set(BYO_SUPPORTED_INTEGRATIONS) | set(LOCKER_OAUTH_ONLY_INTEGRATIONS)
)


def _provider_primary_key_name(integration: str) -> str:
    if integration in LOCKER_OAUTH_ONLY_INTEGRATIONS:
        return "access_token"
    if integration == "apify":
        return "api_token"
    return "api_key"


def _provider_status_details(
    configured_map: dict[tuple[str, str], dict[str, Any]],
    integration: str,
) -> dict[str, Any]:
    details: dict[str, Any] = {}
    if integration == "nimbleway":
        social_secret = configured_map.get((integration, "social_auth"))
        details["social_auth_configured"] = bool(social_secret)
        details["social_auth_suffix"] = social_secret["key_suffix"] if social_secret else None
        details["social_auth_connected_at"] = (
            social_secret["created_at"] if social_secret else None
        )
        details["social_auth_last_validated_at"] = (
            social_secret["validated_at"] if social_secret else None
        )
    return details


def _sanitize_credential(value: str) -> str:
    """
    Sanitize credential value to ensure ASCII-encodable content.

    This handles common issues from copy-paste:
    - Smart quotes -> straight quotes
    - Fancy dashes -> regular dashes
    - Non-breaking spaces -> regular spaces
    - Other non-ASCII characters are stripped

    Args:
        value: Raw credential value (potentially with copy-paste artifacts)

    Returns:
        Sanitized credential value
    """
    replacements = {
        "\u2018": "'",  # Left single quote
        "\u2019": "'",  # Right single quote
        "\u201c": '"',  # Left double quote
        "\u201d": '"',  # Right double quote
        "\u2013": "-",  # En dash
        "\u2014": "-",  # Em dash
        "\u00a0": " ",  # Non-breaking space
    }
    for char, replacement in replacements.items():
        value = value.replace(char, replacement)

    # Strip whitespace from ends (common copy-paste issue)
    value = value.strip()

    # Check if there are any remaining non-ASCII characters
    try:
        value.encode("ascii")
    except UnicodeEncodeError:
        original = value
        value = value.encode("ascii", errors="ignore").decode("ascii")
        logger.warning(
            "Stripped non-ASCII characters from credential: original_len=%d sanitized_len=%d",
            len(original),
            len(value),
        )

    return value


class SaaSSecretBackend:
    """
    Async-first credential backend for SaaS workspaces.

    Stores secrets in PostgreSQL with KMS envelope encryption.
    Secrets are scoped to workspace_id (passed as 'profile' parameter).

    Note: This backend is designed for async contexts. For sync contexts,
    use asyncio.run() or get_event_loop().run_until_complete().
    """

    def __init__(
        self,
        encryption: Optional[KMSEnvelopeEncryption] = None,
    ):
        """
        Initialize SaaS secret backend.

        Args:
            encryption: KMS envelope encryption service. If None, uses default.
        """
        self._encryption = encryption

    @property
    def encryption(self) -> KMSEnvelopeEncryption:
        """Lazy-load encryption service."""
        if self._encryption is None:
            self._encryption = get_envelope_encryption()
        return self._encryption

    @property
    def source(self) -> SecretSource:
        return SecretSource.SAAS

    @property
    def is_available(self) -> bool:
        return True

    @property
    def is_read_only(self) -> bool:
        return False

    async def get_secret(
        self,
        db: AsyncSession,
        integration: str,
        key: str,
        workspace_id: str,
    ) -> Optional[SecretValue]:
        """
        Retrieve a secret from the database.

        Args:
            db: Async database session
            integration: Integration name (e.g., 'fullenrich')
            key: Key name (e.g., 'api_key')
            workspace_id: Workspace ID (scope)

        Returns:
            SecretValue if found, None otherwise
        """
        stmt = select(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id,
            WorkspaceSecret.integration == integration,
            WorkspaceSecret.key_name == key,
        )
        result = await db.execute(stmt)
        record = result.scalar_one_or_none()

        if record is None:
            return None

        # Decrypt the secret
        try:
            encrypted = EncryptedSecret(
                encrypted_value=record.encrypted_value,
                encryption_nonce=record.encryption_nonce,
                encrypted_dek=record.encrypted_dek,
                kms_key_version=record.kms_key_version,
            )
            plaintext = self.encryption.decrypt(encrypted)
        except Exception as e:
            logger.error(
                f"Failed to decrypt secret for {workspace_id}/{integration}/{key}: {e}"
            )
            return None

        return SecretValue(
            value=plaintext,
            source=SecretSource.SAAS,
            integration=integration,
            key=key,
            profile=workspace_id,
            last_rotated=record.updated_at,
            metadata={
                "key_suffix": record.key_suffix,
                "validated_at": record.validated_at.isoformat() if record.validated_at else None,
                "terms_accepted_at": record.terms_accepted_at.isoformat()
                if record.terms_accepted_at
                else None,
                "created_by": record.created_by,
            },
        )

    async def get_secret_by_id(
        self,
        db: AsyncSession,
        secret_id: str | UUID,
        workspace_id: str,
        integration: Optional[str] = None,
    ) -> Optional[SecretValue]:
        """
        Retrieve a secret by database ID (metadata + value).

        Args:
            db: Async database session
            secret_id: WorkspaceSecret ID
            workspace_id: Workspace ID (scope)
            integration: Optional integration filter

        Returns:
            SecretValue if found, None otherwise
        """
        try:
            secret_uuid = secret_id if isinstance(secret_id, UUID) else UUID(str(secret_id))
        except (TypeError, ValueError):
            return None

        stmt = select(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id,
            WorkspaceSecret.id == secret_uuid,
        )
        if integration:
            stmt = stmt.where(WorkspaceSecret.integration == integration)

        result = await db.execute(stmt)
        record = result.scalar_one_or_none()
        if record is None:
            return None

        try:
            encrypted = EncryptedSecret(
                encrypted_value=record.encrypted_value,
                encryption_nonce=record.encryption_nonce,
                encrypted_dek=record.encrypted_dek,
                kms_key_version=record.kms_key_version,
            )
            plaintext = self.encryption.decrypt(encrypted)
        except Exception as e:
            logger.error(
                f"Failed to decrypt secret for {workspace_id}/{record.integration}/{record.key_name}: {e}"
            )
            return None

        return SecretValue(
            value=plaintext,
            source=SecretSource.SAAS,
            integration=record.integration,
            key=record.key_name,
            profile=workspace_id,
            last_rotated=record.updated_at,
            metadata={
                "key_suffix": record.key_suffix,
                "validated_at": record.validated_at.isoformat() if record.validated_at else None,
                "terms_accepted_at": record.terms_accepted_at.isoformat()
                if record.terms_accepted_at
                else None,
                "created_by": record.created_by,
            },
        )

    async def set_secret(
        self,
        db: AsyncSession,
        integration: str,
        key: str,
        value: str,
        workspace_id: str,
        created_by: Optional[str] = None,
        validated_at: Optional[datetime] = None,
        terms_accepted_at: Optional[datetime] = None,
    ) -> WorkspaceSecret:
        """
        Store or update a secret in the database.

        Args:
            db: Async database session
            integration: Integration name (e.g., 'fullenrich')
            key: Key name (e.g., 'api_key')
            value: Secret value (plaintext - will be encrypted)
            workspace_id: Workspace ID (scope)
            created_by: User who created/updated the secret
            validated_at: Timestamp of last successful validation

        Returns:
            The created/updated WorkspaceSecret record
        """
        # Check if integration is supported
        supported_integrations = BYO_SUPPORTED_INTEGRATIONS | OAUTH_SUPPORTED_INTEGRATIONS
        if integration not in supported_integrations:
            raise ValueError(
                f"Integration '{integration}' is not supported for BYO Provider Locker. "
                f"Supported: {', '.join(sorted(supported_integrations))}"
            )

        # Sanitize credential value (fix smart quotes from copy-paste)
        value = _sanitize_credential(value)

        # Encrypt the secret
        encrypted = self.encryption.encrypt(value)
        key_suffix = get_key_suffix(value, length=4)

        # Check if secret already exists
        stmt = select(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id,
            WorkspaceSecret.integration == integration,
            WorkspaceSecret.key_name == key,
        )
        result = await db.execute(stmt)
        existing = result.scalar_one_or_none()

        if existing:
            # Update existing secret
            existing.encrypted_value = encrypted.encrypted_value
            existing.encryption_nonce = encrypted.encryption_nonce
            existing.encrypted_dek = encrypted.encrypted_dek
            existing.kms_key_version = encrypted.kms_key_version
            existing.key_suffix = key_suffix
            existing.created_by = created_by or existing.created_by
            if validated_at:
                existing.validated_at = validated_at
            if terms_accepted_at:
                existing.terms_accepted_at = terms_accepted_at
            db.add(existing)
            await db.flush()
            return existing
        else:
            # Create new secret
            now = datetime.now(timezone.utc)
            record = WorkspaceSecret(
                workspace_id=workspace_id,
                integration=integration,
                key_name=key,
                encrypted_value=encrypted.encrypted_value,
                encryption_nonce=encrypted.encryption_nonce,
                encrypted_dek=encrypted.encrypted_dek,
                kms_key_version=encrypted.kms_key_version,
                key_suffix=key_suffix,
                created_by=created_by,
                validated_at=validated_at,
                terms_accepted_at=terms_accepted_at,
            )
            db.add(record)
            await db.flush()
            return record

    async def delete_secret(
        self,
        db: AsyncSession,
        integration: str,
        key: str,
        workspace_id: str,
    ) -> bool:
        """
        Delete a secret from the database.

        Args:
            db: Async database session
            integration: Integration name
            key: Key name
            workspace_id: Workspace ID

        Returns:
            True if deleted, False if not found
        """
        stmt = delete(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id,
            WorkspaceSecret.integration == integration,
            WorkspaceSecret.key_name == key,
        )
        result = await db.execute(stmt)
        return result.rowcount > 0

    async def list_secrets(
        self,
        db: AsyncSession,
        workspace_id: str,
        integration: Optional[str] = None,
    ) -> List[dict]:
        """
        List secrets for a workspace (metadata only, not values).

        Args:
            db: Async database session
            workspace_id: Workspace ID
            integration: Optional filter by integration

        Returns:
            List of secret metadata dicts
        """
        stmt = select(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id
        )
        if integration:
            stmt = stmt.where(WorkspaceSecret.integration == integration)

        result = await db.execute(stmt)
        records = result.scalars().all()

        return [
            {
                "id": str(r.id),
                "integration": r.integration,
                "key_name": r.key_name,
                "key_suffix": r.key_suffix,
                "created_at": r.created_at.isoformat() if r.created_at else None,
                "updated_at": r.updated_at.isoformat() if r.updated_at else None,
                "validated_at": r.validated_at.isoformat() if r.validated_at else None,
                "terms_accepted_at": r.terms_accepted_at.isoformat()
                if r.terms_accepted_at
                else None,
                "created_by": r.created_by,
            }
            for r in records
        ]

    async def mark_validated(
        self,
        db: AsyncSession,
        integration: str,
        key: str,
        workspace_id: str,
    ) -> bool:
        """
        Mark a secret as validated (successful provider API call).

        Args:
            db: Async database session
            integration: Integration name
            key: Key name
            workspace_id: Workspace ID

        Returns:
            True if updated, False if not found
        """
        stmt = select(WorkspaceSecret).where(
            WorkspaceSecret.workspace_id == workspace_id,
            WorkspaceSecret.integration == integration,
            WorkspaceSecret.key_name == key,
        )
        result = await db.execute(stmt)
        record = result.scalar_one_or_none()

        if record is None:
            return False

        record.validated_at = datetime.now(timezone.utc)
        db.add(record)
        await db.flush()
        return True

    async def log_audit(
        self,
        db: AsyncSession,
        workspace_id: str,
        integration: str,
        action: SecretAuditAction,
        actor_email: Optional[str] = None,
        actor_user_id: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
    ) -> SecretAuditLog:
        """
        Log an audit entry for secret operations.

        Args:
            db: Async database session
            workspace_id: Workspace ID
            integration: Integration name
            action: Action type (added, replaced, removed, validated)
            actor_email: Email of the user performing the action
            actor_user_id: User ID of the actor
            ip_address: Client IP address
            user_agent: Client user agent

        Returns:
            The created audit log entry
        """
        entry = SecretAuditLog(
            workspace_id=workspace_id,
            integration=integration,
            action=action,
            actor_email=actor_email,
            actor_user_id=actor_user_id,
            ip_address=ip_address,
            user_agent=user_agent,
        )
        db.add(entry)
        await db.flush()
        return entry

    async def get_provider_status(
        self,
        db: AsyncSession,
        workspace_id: str,
    ) -> List[dict]:
        """
        Get status of all locker-visible providers for a workspace.

        Returns list with all supported providers and their connection status.

        Args:
            db: Async database session
            workspace_id: Workspace ID

        Returns:
            List of provider status dicts
        """
        # Get configured secrets for this workspace
        configured = await self.list_secrets(db, workspace_id)
        configured_map = {
            (s["integration"], s["key_name"]): s for s in configured
        }

        statuses = []

        # Check each supported integration
        for integration in sorted(LOCKER_SUPPORTED_INTEGRATIONS):
            key = _provider_primary_key_name(integration)
            secret_info = configured_map.get((integration, key))
            details = _provider_status_details(configured_map, integration)

            if secret_info:
                statuses.append({
                    "provider": integration,
                    "status": "connected",
                    "key_suffix": (
                        None
                        if integration in LOCKER_OAUTH_ONLY_INTEGRATIONS
                        else secret_info["key_suffix"]
                    ),
                    "connected_at": secret_info["created_at"],
                    "last_validated_at": secret_info["validated_at"],
                    "details": details,
                })
            else:
                statuses.append({
                    "provider": integration,
                    "status": "not_connected",
                    "key_suffix": None,
                    "connected_at": None,
                    "last_validated_at": None,
                    "details": details,
                })

        # FoundryGraph is always available (internal service)
        statuses.append({
            "provider": "foundrygraph",
            "status": "available",
            "key_suffix": None,
            "connected_at": None,
            "last_validated_at": None,
            "details": {},
        })

        return statuses


# Global instance (lazy initialization)
_saas_backend: Optional[SaaSSecretBackend] = None


def get_saas_secret_backend() -> SaaSSecretBackend:
    """Get or create the global SaaS secret backend instance."""
    global _saas_backend
    if _saas_backend is None:
        _saas_backend = SaaSSecretBackend()
    return _saas_backend
