"""
Tenant Member Repository
Repository for tenant membership operations
"""

from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text, and_
from datetime import datetime, timedelta
import structlog
import json

from .base import TenantBaseRepository
from app.models.tenants import TenantMember
from shared.database.exceptions import DatabaseError, ValidationError, DuplicateRecordError
from shared.config.base import is_internal_service

logger = structlog.get_logger()


class TenantMemberRepository(TenantBaseRepository):
    """Repository for tenant member operations"""
    
    def __init__(self, model_class, session: AsyncSession, cache_ttl: Optional[int] = 300):
        # Member data changes more frequently, shorter cache time (5 minutes)
        super().__init__(model_class, session, cache_ttl)
    
    async def create_membership(self, membership_data: Dict[str, Any]) -> TenantMember:
        """Create a new tenant membership with validation"""
        try:
            # Validate membership data
            validation_result = self._validate_tenant_data(
                membership_data, 
                ["tenant_id", "user_id", "role"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid membership data: {validation_result['errors']}")
            
            # Check for existing membership
            existing_membership = await self.get_membership(
                membership_data["tenant_id"], 
                membership_data["user_id"]
            )
            
            if existing_membership and existing_membership.is_active:
                raise DuplicateRecordError(f"User is already an active member of this tenant")
            
            # Set default values
            if "is_active" not in membership_data:
                membership_data["is_active"] = True
            if "joined_at" not in membership_data:
                membership_data["joined_at"] = datetime.utcnow()
            
            # Set permissions based on role
            if "permissions" not in membership_data:
                membership_data["permissions"] = self._get_default_permissions(
                    membership_data["role"]
                )
            
            # If reactivating existing membership
            if existing_membership and not existing_membership.is_active:
                # Update existing membership
                update_data = {
                    key: value for key, value in membership_data.items() 
                    if key not in ["tenant_id", "user_id"]
                }
                membership = await self.update(existing_membership.id, update_data)
            else:
                # Create new membership
                membership = await self.create(membership_data)
            
            logger.info("Tenant membership created",
                       membership_id=membership.id,
                       tenant_id=membership.tenant_id,
                       user_id=membership.user_id,
                       role=membership.role)
            
            return membership
            
        except (ValidationError, DuplicateRecordError):
            raise
        except Exception as e:
            logger.error("Failed to create membership",
                        tenant_id=membership_data.get("tenant_id"),
                        user_id=membership_data.get("user_id"),
                        error=str(e))
            raise DatabaseError(f"Failed to create membership: {str(e)}")
    
    async def get_membership(self, tenant_id: str, user_id: str) -> Optional[TenantMember]:
        """Get specific membership by tenant and user"""
        try:
            # Validate that user_id is a proper UUID format for actual users
            # Service names like 'inventory-service' should be handled differently
            import uuid
            try:
                uuid.UUID(user_id)
                is_valid_uuid = True
            except ValueError:
                is_valid_uuid = False

            # For internal service access, return None to indicate no user membership
            # Service access should be handled at the API layer
            if not is_valid_uuid:
                if is_internal_service(user_id):
                    # This is a known internal service request, return None
                    # Service access is granted at the API endpoint level
                    logger.debug("Internal service detected in membership lookup",
                                service=user_id,
                                tenant_id=tenant_id)
                    return None
                elif user_id == "unknown-service":
                    # Special handling for 'unknown-service' which commonly occurs in demo sessions
                    # This happens when service identification fails during demo operations
                    logger.warning("Demo session service identification issue",
                                  service=user_id,
                                  tenant_id=tenant_id,
                                  message="Service not properly identified - likely demo session context")
                    return None
                else:
                    # This is an unknown service
                    # Return None to prevent database errors, but log a warning
                    logger.warning("Unknown service detected in membership lookup",
                                  service=user_id,
                                  tenant_id=tenant_id,
                                  message="Service not in internal services registry")
                    return None

            memberships = await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "user_id": user_id
                },
                limit=1,
                order_by="created_at",
                order_desc=True
            )
            return memberships[0] if memberships else None
        except Exception as e:
            logger.error("Failed to get membership",
                        tenant_id=tenant_id,
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to get membership: {str(e)}")
    
    async def get_tenant_members(
        self,
        tenant_id: str,
        active_only: bool = True,
        role: str = None,
        include_user_info: bool = False
    ) -> List[TenantMember]:
        """Get all members of a tenant with optional user info enrichment"""
        try:
            filters = {"tenant_id": tenant_id}

            if active_only:
                filters["is_active"] = True

            if role:
                filters["role"] = role

            members = await self.get_multi(
                filters=filters,
                order_by="joined_at",
                order_desc=False
            )

            # If include_user_info is True, enrich with user data from auth service
            if include_user_info and members:
                members = await self._enrich_members_with_user_info(members)

            return members
        except Exception as e:
            logger.error("Failed to get tenant members",
                        tenant_id=tenant_id,
                        error=str(e))
            raise DatabaseError(f"Failed to get members: {str(e)}")

    async def _enrich_members_with_user_info(self, members: List[TenantMember]) -> List[TenantMember]:
        """Enrich member objects with user information from auth service using batch endpoint"""
        try:
            import httpx
            import os

            if not members:
                return members

            # Get unique user IDs
            user_ids = list(set([str(member.user_id) for member in members]))

            if not user_ids:
                return members

            # Fetch user data from auth service using batch endpoint
            # Using internal service communication
            auth_service_url = os.getenv('AUTH_SERVICE_URL', 'http://auth-service:8000')

            user_data_map = {}
            async with httpx.AsyncClient() as client:
                try:
                    # Use batch endpoint for efficiency
                    response = await client.post(
                        f"{auth_service_url}/api/v1/auth/users/batch",
                        json={"user_ids": user_ids},
                        timeout=10.0,
                        headers={"x-internal-service": "tenant-service"}
                    )

                    if response.status_code == 200:
                        batch_result = response.json()
                        user_data_map = batch_result.get("users", {})
                        logger.info(
                            "Batch user fetch successful",
                            requested_count=len(user_ids),
                            found_count=batch_result.get("found_count", 0)
                        )
                    else:
                        logger.warning(
                            "Batch user fetch failed, falling back to individual calls",
                            status_code=response.status_code
                        )
                        # Fallback to individual calls if batch fails
                        for user_id in user_ids:
                            try:
                                response = await client.get(
                                    f"{auth_service_url}/api/v1/auth/users/{user_id}",
                                    timeout=5.0,
                                    headers={"x-internal-service": "tenant-service"}
                                )
                                if response.status_code == 200:
                                    user_data = response.json()
                                    user_data_map[user_id] = user_data
                            except Exception as e:
                                logger.warning(f"Failed to fetch user data for {user_id}", error=str(e))
                                continue

                except Exception as e:
                    logger.warning("Batch user fetch failed, falling back to individual calls", error=str(e))
                    # Fallback to individual calls
                    for user_id in user_ids:
                        try:
                            response = await client.get(
                                f"{auth_service_url}/api/v1/auth/users/{user_id}",
                                timeout=5.0,
                                headers={"x-internal-service": "tenant-service"}
                            )
                            if response.status_code == 200:
                                user_data = response.json()
                                user_data_map[user_id] = user_data
                        except Exception as e:
                            logger.warning(f"Failed to fetch user data for {user_id}", error=str(e))
                            continue

            # Enrich members with user data
            for member in members:
                user_id_str = str(member.user_id)
                if user_id_str in user_data_map and user_data_map[user_id_str] is not None:
                    user_data = user_data_map[user_id_str]
                    # Add user fields as attributes to the member object
                    member.user_email = user_data.get("email")
                    member.user_full_name = user_data.get("full_name")
                    member.user = user_data  # Store full user object for compatibility
                else:
                    # Set defaults for missing users
                    member.user_email = None
                    member.user_full_name = "Unknown User"
                    member.user = None

            return members

        except Exception as e:
            logger.warning("Failed to enrich members with user info", error=str(e))
            # Return members without enrichment if it fails
            return members
    
    async def get_user_memberships(
        self, 
        user_id: str, 
        active_only: bool = True
    ) -> List[TenantMember]:
        """Get all tenants a user is a member of"""
        try:
            filters = {"user_id": user_id}
            
            if active_only:
                filters["is_active"] = True
            
            return await self.get_multi(
                filters=filters,
                order_by="joined_at",
                order_desc=True
            )
        except Exception as e:
            logger.error("Failed to get user memberships",
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to get memberships: {str(e)}")
    
    async def verify_user_access(
        self, 
        user_id: str, 
        tenant_id: str
    ) -> Dict[str, Any]:
        """Verify if user has access to tenant and return access details"""
        try:
            membership = await self.get_membership(tenant_id, user_id)
            
            if not membership or not membership.is_active:
                return {
                    "has_access": False,
                    "role": "none",
                    "permissions": []
                }
            
            # Parse permissions
            permissions = []
            if membership.permissions:
                try:
                    permissions = json.loads(membership.permissions)
                except json.JSONDecodeError:
                    logger.warning("Invalid permissions JSON for membership",
                                  membership_id=membership.id)
                    permissions = self._get_default_permissions(membership.role)
            
            return {
                "has_access": True,
                "role": membership.role,
                "permissions": permissions,
                "membership_id": str(membership.id),
                "joined_at": membership.joined_at.isoformat() if membership.joined_at else None
            }
            
        except Exception as e:
            logger.error("Failed to verify user access",
                        user_id=user_id,
                        tenant_id=tenant_id,
                        error=str(e))
            return {
                "has_access": False,
                "role": "none",
                "permissions": []
            }
    
    async def update_member_role(
        self, 
        tenant_id: str, 
        user_id: str, 
        new_role: str,
        updated_by: str = None
    ) -> Optional[TenantMember]:
        """Update member role and permissions"""
        try:
            valid_roles = ["owner", "admin", "member", "viewer"]
            if new_role not in valid_roles:
                raise ValidationError(f"Invalid role. Must be one of: {valid_roles}")
            
            membership = await self.get_membership(tenant_id, user_id)
            if not membership:
                raise ValidationError("Membership not found")
            
            # Get new permissions based on role
            new_permissions = self._get_default_permissions(new_role)
            
            updated_membership = await self.update(membership.id, {
                "role": new_role,
                "permissions": json.dumps(new_permissions)
            })
            
            logger.info("Member role updated",
                       membership_id=membership.id,
                       tenant_id=tenant_id,
                       user_id=user_id,
                       old_role=membership.role,
                       new_role=new_role,
                       updated_by=updated_by)
            
            return updated_membership
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to update member role",
                        tenant_id=tenant_id,
                        user_id=user_id,
                        new_role=new_role,
                        error=str(e))
            raise DatabaseError(f"Failed to update role: {str(e)}")
    
    async def deactivate_membership(
        self, 
        tenant_id: str, 
        user_id: str,
        deactivated_by: str = None
    ) -> Optional[TenantMember]:
        """Deactivate a membership (remove user from tenant)"""
        try:
            membership = await self.get_membership(tenant_id, user_id)
            if not membership:
                raise ValidationError("Membership not found")
            
            # Don't allow deactivating the owner
            if membership.role == "owner":
                raise ValidationError("Cannot deactivate the owner membership")
            
            updated_membership = await self.update(membership.id, {
                "is_active": False
            })
            
            logger.info("Membership deactivated",
                       membership_id=membership.id,
                       tenant_id=tenant_id,
                       user_id=user_id,
                       deactivated_by=deactivated_by)
            
            return updated_membership
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to deactivate membership",
                        tenant_id=tenant_id,
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to deactivate membership: {str(e)}")
    
    async def reactivate_membership(
        self, 
        tenant_id: str, 
        user_id: str,
        reactivated_by: str = None
    ) -> Optional[TenantMember]:
        """Reactivate a deactivated membership"""
        try:
            membership = await self.get_membership(tenant_id, user_id)
            if not membership:
                raise ValidationError("Membership not found")
            
            updated_membership = await self.update(membership.id, {
                "is_active": True,
                "joined_at": datetime.utcnow()  # Update join date
            })
            
            logger.info("Membership reactivated",
                       membership_id=membership.id,
                       tenant_id=tenant_id,
                       user_id=user_id,
                       reactivated_by=reactivated_by)
            
            return updated_membership
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to reactivate membership",
                        tenant_id=tenant_id,
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to reactivate membership: {str(e)}")
    
    async def get_membership_statistics(self, tenant_id: str) -> Dict[str, Any]:
        """Get membership statistics for a tenant"""
        try:
            # Get counts by role
            role_query = text("""
                SELECT role, COUNT(*) as count 
                FROM tenant_members 
                WHERE tenant_id = :tenant_id AND is_active = true
                GROUP BY role
                ORDER BY count DESC
            """)
            
            result = await self.session.execute(role_query, {"tenant_id": tenant_id})
            members_by_role = {row.role: row.count for row in result.fetchall()}
            
            # Get basic counts
            total_members = await self.count(filters={"tenant_id": tenant_id})
            active_members = await self.count(filters={
                "tenant_id": tenant_id,
                "is_active": True
            })
            
            # Get recent activity (members joined in last 30 days)
            thirty_days_ago = datetime.utcnow() - timedelta(days=30)
            recent_joins = len(await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "is_active": True
                },
                limit=1000  # High limit to get accurate count
            ))
            
            # Filter for recent joins (manual filtering since we can't use date range in filters easily)
            recent_members = 0
            all_active_members = await self.get_tenant_members(tenant_id, active_only=True)
            for member in all_active_members:
                if member.joined_at and member.joined_at >= thirty_days_ago:
                    recent_members += 1
            
            return {
                "total_members": total_members,
                "active_members": active_members,
                "inactive_members": total_members - active_members,
                "members_by_role": members_by_role,
                "recent_joins_30d": recent_members
            }
            
        except Exception as e:
            logger.error("Failed to get membership statistics",
                        tenant_id=tenant_id,
                        error=str(e))
            return {
                "total_members": 0,
                "active_members": 0,
                "inactive_members": 0,
                "members_by_role": {},
                "recent_joins_30d": 0
            }
    
    def _get_default_permissions(self, role: str) -> str:
        """Get default permissions JSON string for a role"""
        permission_map = {
            "owner": ["read", "write", "admin", "delete"],
            "admin": ["read", "write", "admin"],
            "member": ["read", "write"],
            "viewer": ["read"]
        }
        
        permissions = permission_map.get(role, ["read"])
        return json.dumps(permissions)
    
    async def bulk_update_permissions(
        self, 
        tenant_id: str, 
        role_permissions: Dict[str, List[str]]
    ) -> int:
        """Bulk update permissions for all members of specific roles"""
        try:
            updated_count = 0
            
            for role, permissions in role_permissions.items():
                members = await self.get_tenant_members(
                    tenant_id, active_only=True, role=role
                )
                
                for member in members:
                    await self.update(member.id, {
                        "permissions": json.dumps(permissions)
                    })
                    updated_count += 1
            
            logger.info("Bulk updated member permissions",
                       tenant_id=tenant_id,
                       updated_count=updated_count,
                       roles=list(role_permissions.keys()))
            
            return updated_count
            
        except Exception as e:
            logger.error("Failed to bulk update permissions",
                        tenant_id=tenant_id,
                        error=str(e))
            raise DatabaseError(f"Bulk permission update failed: {str(e)}")
    
    async def cleanup_inactive_memberships(self, days_old: int = 180) -> int:
        """Clean up old inactive memberships"""
        try:
            cutoff_date = datetime.utcnow() - timedelta(days=days_old)
            
            query_text = """
                DELETE FROM tenant_members 
                WHERE is_active = false 
                AND created_at < :cutoff_date
            """
            
            result = await self.session.execute(text(query_text), {"cutoff_date": cutoff_date})
            deleted_count = result.rowcount
            
            logger.info("Cleaned up inactive memberships",
                       deleted_count=deleted_count,
                       days_old=days_old)
            
            return deleted_count
            
        except Exception as e:
            logger.error("Failed to cleanup inactive memberships",
                        error=str(e))
            raise DatabaseError(f"Cleanup failed: {str(e)}")
