# ================================================================
# shared/auth/tenant_access.py - Complete Implementation
# ================================================================
"""
Tenant access control utilities for microservices
Provides both gateway-level and service-level tenant access verification
"""

import re
from typing import Dict, Any, Optional
import httpx
import structlog
from fastapi import HTTPException, Depends
import asyncio

# Import FastAPI dependencies
from shared.auth.decorators import get_current_user_dep

# Import settings (adjust import path based on your project structure)
try:
    from app.core.config import settings
except ImportError:
    try:
        from core.config import settings
    except ImportError:
        # Fallback for different project structures
        import os
        class Settings:
            TENANT_SERVICE_URL = os.getenv("TENANT_SERVICE_URL", "http://tenant-service:8000")
        settings = Settings()

# Setup logging
logger = structlog.get_logger()

class TenantAccessManager:
    """
    Centralized tenant access management for both gateway and service level
    """
    
    def __init__(self, redis_client=None):
        """
        Initialize tenant access manager
        
        Args:
            redis_client: Optional Redis client for caching
        """
        self.redis_client = redis_client
    
    async def verify_basic_tenant_access(self, user_id: str, tenant_id: str) -> bool:
        """
        Gateway-level: Basic tenant access verification with caching
        
        Args:
            user_id: User ID to verify
            tenant_id: Tenant ID to check access for
            
        Returns:
            bool: True if user has access to tenant
        """
        # Check cache first (5-minute TTL)
        cache_key = f"tenant_access:{user_id}:{tenant_id}"
        if self.redis_client:
            try:
                cached_result = await self.redis_client.get(cache_key)
                if cached_result is not None:
                    return cached_result.decode() == "true" if isinstance(cached_result, bytes) else cached_result == "true"
            except Exception as cache_error:
                logger.warning(f"Cache lookup failed: {cache_error}")
        
        # Verify with tenant service
        try:
            async with httpx.AsyncClient(timeout=2.0) as client:  # Short timeout for gateway
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/access/{user_id}"
                )

                has_access = response.status_code == 200

                # If direct access check fails, check hierarchical access
                if not has_access:
                    hierarchical_access = await self._check_hierarchical_access(user_id, tenant_id)
                    has_access = hierarchical_access

                # Cache result (5 minutes)
                if self.redis_client:
                    try:
                        await self.redis_client.setex(cache_key, 300, "true" if has_access else "false")
                    except Exception as cache_error:
                        logger.warning(f"Cache set failed: {cache_error}")

                logger.debug(f"Tenant access check",
                           user_id=user_id,
                           tenant_id=tenant_id,
                           has_access=has_access)

                return has_access
                
        except asyncio.TimeoutError:
            logger.error(f"Timeout verifying tenant access: user={user_id}, tenant={tenant_id}")
            # Fail open for availability (let service handle detailed check)
            return True
        except httpx.RequestError as e:
            logger.error(f"Request error verifying tenant access: {e}")
            # Fail open for availability
            return True
        except Exception as e:
            logger.error(f"Gateway tenant access verification failed: {e}")
            # Fail open for availability (let service handle detailed check)
            return True

    async def _check_hierarchical_access(self, user_id: str, tenant_id: str) -> bool:
        """
        Check if user has hierarchical access (parent tenant access to child)

        Args:
            user_id: User ID to verify
            tenant_id: Target tenant ID to check access for

        Returns:
            bool: True if user has hierarchical access to the tenant
        """
        try:
            async with httpx.AsyncClient(timeout=3.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/hierarchy"
                )

                if response.status_code == 200:
                    hierarchy_data = response.json()
                    parent_tenant_id = hierarchy_data.get("parent_tenant_id")

                    # If this is a child tenant, check if user has access to parent
                    if parent_tenant_id:
                        # Check if user has access to parent tenant
                        parent_access = await self._check_parent_access(user_id, parent_tenant_id)
                        if parent_access:
                            # For aggregated data only, allow parent access to child
                            # Detailed child data requires direct access
                            user_role = await self.get_user_role_in_tenant(user_id, parent_tenant_id)
                            if user_role in ["owner", "admin", "network_admin"]:
                                return True

                return False
        except Exception as e:
            logger.error(f"Failed to check hierarchical access: {e}")
            return False

    async def _check_parent_access(self, user_id: str, parent_tenant_id: str) -> bool:
        """
        Check if user has access to parent tenant (owner, admin, or network_admin role)

        Args:
            user_id: User ID
            parent_tenant_id: Parent tenant ID

        Returns:
            bool: True if user has access to parent tenant
        """
        user_role = await self.get_user_role_in_tenant(user_id, parent_tenant_id)
        return user_role in ["owner", "admin", "network_admin"]

    async def verify_hierarchical_access(self, user_id: str, tenant_id: str) -> dict:
        """
        Verify hierarchical access and return access type and permissions

        Args:
            user_id: User ID
            tenant_id: Target tenant ID

        Returns:
            dict: Access information including access_type, can_view_children, etc.
        """
        # First check direct access
        direct_access = await self._check_direct_access(user_id, tenant_id)

        if direct_access:
            return {
                "access_type": "direct",
                "has_access": True,
                "can_view_children": False,
                "tenant_id": tenant_id
            }

        # Check if this is a child tenant and user has parent access
        hierarchy_info = await self._get_tenant_hierarchy(tenant_id)

        if hierarchy_info and hierarchy_info.get("parent_tenant_id"):
            parent_tenant_id = hierarchy_info["parent_tenant_id"]
            parent_access = await self._check_parent_access(user_id, parent_tenant_id)

            if parent_access:
                user_role = await self.get_user_role_in_tenant(user_id, parent_tenant_id)

                # Network admins have full access across entire hierarchy
                if user_role == "network_admin":
                    return {
                        "access_type": "hierarchical",
                        "has_access": True,
                        "tenant_id": tenant_id,
                        "parent_tenant_id": parent_tenant_id,
                        "is_network_admin": True,
                        "can_view_children": True
                    }
                # Regular admins have read-only access to children aggregated data
                elif user_role in ["owner", "admin"]:
                    return {
                        "access_type": "hierarchical",
                        "has_access": True,
                        "tenant_id": tenant_id,
                        "parent_tenant_id": parent_tenant_id,
                        "is_network_admin": False,
                        "can_view_children": True  # Can view aggregated data, not detailed
                    }

        return {
            "access_type": "none",
            "has_access": False,
            "tenant_id": tenant_id,
            "can_view_children": False
        }

    async def _check_direct_access(self, user_id: str, tenant_id: str) -> bool:
        """
        Check direct access to tenant (without hierarchy)
        """
        try:
            async with httpx.AsyncClient(timeout=2.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/access/{user_id}"
                )
                return response.status_code == 200
        except Exception as e:
            logger.error(f"Failed to check direct access: {e}")
            return False

    async def _get_tenant_hierarchy(self, tenant_id: str) -> dict:
        """
        Get tenant hierarchy information

        Args:
            tenant_id: Tenant ID

        Returns:
            dict: Hierarchy information
        """
        try:
            async with httpx.AsyncClient(timeout=3.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/hierarchy"
                )

                if response.status_code == 200:
                    return response.json()
                return {}
        except Exception as e:
            logger.error(f"Failed to get tenant hierarchy: {e}")
            return {}

    async def get_accessible_tenants_hierarchy(self, user_id: str) -> list:
        """
        Get all tenants a user has access to, organized in hierarchy

        Args:
            user_id: User ID

        Returns:
            list: List of tenants with hierarchy structure
        """
        try:
            async with httpx.AsyncClient(timeout=5.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/users/{user_id}/hierarchy"
                )
                if response.status_code == 200:
                    tenants = response.json()
                    logger.debug(f"Retrieved user tenants with hierarchy",
                               user_id=user_id,
                               tenant_count=len(tenants))
                    return tenants
                else:
                    logger.warning(f"Failed to get user tenants hierarchy: {response.status_code}")
                    return []
        except Exception as e:
            logger.error(f"Failed to get user tenants hierarchy: {e}")
            return []

    async def get_user_role_in_tenant(self, user_id: str, tenant_id: str) -> Optional[str]:
        """
        Get user's role within a specific tenant

        Args:
            user_id: User ID
            tenant_id: Tenant ID

        Returns:
            Optional[str]: User's role in tenant (owner, admin, manager, user, network_admin) or None
        """
        try:
            async with httpx.AsyncClient(timeout=3.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/members/{user_id}"
                )
                if response.status_code == 200:
                    data = response.json()
                    role = data.get("role")
                    logger.debug(f"User role in tenant",
                               user_id=user_id,
                               tenant_id=tenant_id,
                               role=role)
                    return role
                elif response.status_code == 404:
                    logger.debug(f"User not found in tenant",
                               user_id=user_id,
                               tenant_id=tenant_id)
                    return None
                else:
                    logger.warning(f"Unexpected response getting user role: {response.status_code}")
                    return None
        except Exception as e:
            logger.error(f"Failed to get user role in tenant: {e}")
            return None
    
    async def verify_resource_permission(
        self, 
        user_id: str, 
        tenant_id: str, 
        resource: str, 
        action: str
    ) -> bool:
        """
        Fine-grained resource permission check (used by services)
        
        Args:
            user_id: User ID
            tenant_id: Tenant ID
            resource: Resource type (sales, training, forecasts, etc.)
            action: Action being performed (read, write, delete, etc.)
            
        Returns:
            bool: True if user has permission
        """
        user_role = await self.get_user_role_in_tenant(user_id, tenant_id)
        
        if not user_role:
            return False
        
        # Role-based permission matrix
        permissions = {
            "owner": ["*"],  # Owners can do everything
            "admin": ["read", "write", "delete", "manage"],
            "manager": ["read", "write"],
            "user": ["read"]
        }
        
        allowed_actions = permissions.get(user_role, [])
        has_permission = "*" in allowed_actions or action in allowed_actions
        
        logger.debug(f"Resource permission check",
                   user_id=user_id,
                   tenant_id=tenant_id,
                   resource=resource,
                   action=action,
                   user_role=user_role,
                   has_permission=has_permission)
        
        return has_permission
    
    async def get_user_tenants(self, user_id: str) -> list:
        """
        Get all tenants a user has access to
        
        Args:
            user_id: User ID
            
        Returns:
            list: List of tenant dictionaries
        """
        try:
            async with httpx.AsyncClient(timeout=5.0) as client:
                response = await client.get(
                    f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/users/{user_id}"
                )
                if response.status_code == 200:
                    tenants = response.json()
                    logger.debug(f"Retrieved user tenants", 
                               user_id=user_id, 
                               tenant_count=len(tenants))
                    return tenants
                else:
                    logger.warning(f"Failed to get user tenants: {response.status_code}")
                    return []
        except Exception as e:
            logger.error(f"Failed to get user tenants: {e}")
            return []

# Global instance for easy import
tenant_access_manager = TenantAccessManager()



# ================================================================
# FASTAPI DEPENDENCIES
# ================================================================

async def verify_tenant_access_dep(
    tenant_id: str,
    current_user: Dict[str, Any] = Depends(get_current_user_dep)
) -> str:
    """
    FastAPI dependency to verify tenant access and return tenant_id
    
    Args:
        tenant_id: Tenant ID from path parameter
        current_user: Current user from auth dependency
        
    Returns:
        str: Validated tenant_id
        
    Raises:
        HTTPException: If user doesn't have access to tenant
    """
    has_access = await tenant_access_manager.verify_basic_tenant_access(current_user["user_id"], tenant_id)
    if not has_access:
        logger.warning(f"Access denied to tenant", 
                     user_id=current_user["user_id"], 
                     tenant_id=tenant_id)
        raise HTTPException(
            status_code=403,
            detail=f"User {current_user['user_id']} does not have access to tenant {tenant_id}"
        )
    
    logger.debug(f"Tenant access verified", 
               user_id=current_user["user_id"], 
               tenant_id=tenant_id)
    
    return tenant_id

async def verify_tenant_permission_dep(
    tenant_id: str,
    resource: str,
    action: str,
    current_user: Dict[str, Any] = Depends(get_current_user_dep)
) -> str:
    """
    FastAPI dependency to verify tenant access AND resource permission
    
    Args:
        tenant_id: Tenant ID from path parameter
        resource: Resource type being accessed
        action: Action being performed
        current_user: Current user from auth dependency
        
    Returns:
        str: Validated tenant_id
        
    Raises:
        HTTPException: If user doesn't have access or permission
    """
    # First verify basic tenant access
    has_access = await tenant_access_manager.verify_basic_tenant_access(current_user["user_id"], tenant_id)
    if not has_access:
        raise HTTPException(
            status_code=403,
            detail=f"Access denied to tenant {tenant_id}"
        )
    
    # Then verify specific resource permission
    has_permission = await tenant_access_manager.verify_resource_permission(
        current_user["user_id"], tenant_id, resource, action
    )
    if not has_permission:
        raise HTTPException(
            status_code=403,
            detail=f"Insufficient permissions for {action} on {resource}"
        )
    
    logger.debug(f"Tenant access and permission verified", 
               user_id=current_user["user_id"], 
               tenant_id=tenant_id,
               resource=resource,
               action=action)
    
    return tenant_id

# ================================================================
# UTILITY FUNCTIONS
# ================================================================

def extract_tenant_id_from_path(path: str) -> Optional[str]:
    """
    More robust tenant ID extraction using regex pattern matching
    Only matches actual tenant-scoped paths with UUID format
    """
    # Pattern for tenant-scoped paths: /api/v1/tenants/{uuid}/...
    tenant_pattern = r'/api/v1/tenants/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/.*'
    
    match = re.match(tenant_pattern, path, re.IGNORECASE)
    if match:
        return match.group(1)

def is_tenant_scoped_path(path: str) -> bool:
    """
    Check if path is tenant-scoped (contains /tenants/{tenant_id}/)
    
    Args:
        path: URL path
        
    Returns:
        bool: True if path is tenant-scoped
    """
    return extract_tenant_id_from_path(path) is not None

# ================================================================
# EXPORTS
# ================================================================

__all__ = [
    # Classes
    "TenantAccessManager",
    "tenant_access_manager",
    
    # Dependencies
    "verify_tenant_access_dep",
    "verify_tenant_permission_dep",
    
    # Utilities
    "extract_tenant_id_from_path",
    "is_tenant_scoped_path"
]