# app/modules/tenancy/services/admin_platform_service.py
"""
Admin Platform service for managing admin-platform assignments.

This module provides:
- Assigning platform admins to platforms
- Removing platform admin access
- Listing platforms for an admin
- Listing admins for a platform
- Promoting/demoting super admin status
"""

import logging
from datetime import UTC, datetime

from sqlalchemy.orm import Session, joinedload

from app.modules.tenancy.exceptions import (
    AdminOperationException,
    CannotModifySelfException,
    PlatformNotFoundException,
    UserAlreadyExistsException,
    UserNotFoundException,
)
from app.modules.tenancy.models import AdminPlatform, Platform, User
from app.modules.tenancy.schemas.auth import UserContext

logger = logging.getLogger(__name__)


class AdminPlatformService:
    """Service class for admin-platform assignment operations."""

    # ============================================================================
    # ADMIN-PLATFORM ASSIGNMENTS
    # ============================================================================

    def assign_admin_to_platform(
        self,
        db: Session,
        admin_user_id: int,
        platform_id: int,
        assigned_by_user_id: int,
    ) -> AdminPlatform:
        """
        Assign a platform admin to a platform.

        Args:
            db: Database session
            admin_user_id: User ID of the admin to assign
            platform_id: Platform ID to assign to
            assigned_by_user_id: Super admin making the assignment

        Returns:
            AdminPlatform: The created assignment

        Raises:
            ValidationException: If user is not an admin or is a super admin
            AdminOperationException: If assignment already exists
        """
        # Verify target user exists and is an admin
        user = db.query(User).filter(User.id == admin_user_id).first()
        if not user:
            raise UserNotFoundException(str(admin_user_id))
        if not user.is_admin:
            raise AdminOperationException(
                operation="assign_admin_to_platform",
                reason="User must be an admin to be assigned to platforms",
            )
        if user.is_super_admin:
            raise AdminOperationException(
                operation="assign_admin_to_platform",
                reason="Super admins don't need platform assignments - they have access to all platforms",
            )

        # Verify platform exists
        platform = db.query(Platform).filter(Platform.id == platform_id).first()
        if not platform:
            raise PlatformNotFoundException(code=str(platform_id))

        # Check if assignment already exists
        existing = (
            db.query(AdminPlatform)
            .filter(
                AdminPlatform.user_id == admin_user_id,
                AdminPlatform.platform_id == platform_id,
            )
            .first()
        )
        if existing:
            if existing.is_active:
                raise AdminOperationException(
                    operation="assign_admin_to_platform",
                    reason=f"Admin already assigned to platform '{platform.code}'",
                )
            # Reactivate existing assignment
            existing.is_active = True
            existing.assigned_at = datetime.now(UTC)
            existing.assigned_by_user_id = assigned_by_user_id
            existing.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(existing)
            logger.info(
                f"Reactivated admin {admin_user_id} access to platform {platform.code} "
                f"by admin {assigned_by_user_id}"
            )
            return existing

        # Create new assignment
        assignment = AdminPlatform(
            user_id=admin_user_id,
            platform_id=platform_id,
            assigned_by_user_id=assigned_by_user_id,
            is_active=True,
        )
        db.add(assignment)
        db.flush()
        db.refresh(assignment)

        logger.info(
            f"Assigned admin {admin_user_id} to platform {platform.code} "
            f"by admin {assigned_by_user_id}"
        )

        return assignment

    def remove_admin_from_platform(
        self,
        db: Session,
        admin_user_id: int,
        platform_id: int,
        removed_by_user_id: int,
    ) -> None:
        """
        Remove admin's access to a platform.

        This soft-deletes by setting is_active=False for audit purposes.

        Args:
            db: Database session
            admin_user_id: User ID of the admin to remove
            platform_id: Platform ID to remove from
            removed_by_user_id: Super admin making the removal

        Raises:
            ValidationException: If assignment doesn't exist
        """
        assignment = (
            db.query(AdminPlatform)
            .filter(
                AdminPlatform.user_id == admin_user_id,
                AdminPlatform.platform_id == platform_id,
            )
            .first()
        )

        if not assignment:
            raise AdminOperationException(
                operation="remove_admin_from_platform",
                reason="Admin is not assigned to this platform",
            )

        assignment.is_active = False
        assignment.updated_at = datetime.now(UTC)
        db.flush()

        logger.info(
            f"Removed admin {admin_user_id} from platform {platform_id} "
            f"by admin {removed_by_user_id}"
        )

    def get_platforms_for_admin(
        self,
        db: Session,
        admin_user_id: int,
        include_inactive: bool = False,
    ) -> list[Platform]:
        """
        Get all platforms an admin can access.

        Args:
            db: Database session
            admin_user_id: User ID of the admin
            include_inactive: Whether to include inactive assignments

        Returns:
            List of Platform objects the admin can access
        """
        query = (
            db.query(Platform)
            .join(AdminPlatform)
            .filter(AdminPlatform.user_id == admin_user_id)
        )

        if not include_inactive:
            query = query.filter(AdminPlatform.is_active == True)

        return query.all()

    def get_all_active_platforms(self, db: Session) -> list[Platform]:
        """
        Get all active platforms (for super admin access).

        Args:
            db: Database session

        Returns:
            List of all active Platform objects
        """
        return db.query(Platform).filter(Platform.is_active == True).all()

    def get_platform_by_id(self, db: Session, platform_id: int) -> Platform | None:
        """
        Get a platform by ID.

        Args:
            db: Database session
            platform_id: Platform ID

        Returns:
            Platform object or None if not found
        """
        return db.query(Platform).filter(Platform.id == platform_id).first()

    def validate_admin_platform_access(
        self,
        user: User | UserContext,
        platform_id: int,
    ) -> None:
        """
        Validate that an admin has access to a platform.

        Args:
            user: User model or UserContext schema (both have can_access_platform)
            platform_id: Platform ID to check

        Raises:
            InsufficientPermissionsException: If user doesn't have access
        """
        from app.modules.tenancy.exceptions import InsufficientPermissionsException

        if not user.can_access_platform(platform_id):
            raise InsufficientPermissionsException(
                "You don't have access to this platform"
            )

    def get_admins_for_platform(
        self,
        db: Session,
        platform_id: int,
        include_inactive: bool = False,
    ) -> list[User]:
        """
        Get all admins assigned to a platform.

        Args:
            db: Database session
            platform_id: Platform ID
            include_inactive: Whether to include inactive assignments

        Returns:
            List of User objects assigned to the platform
        """
        # Explicit join condition needed because AdminPlatform has two FKs to User
        # (user_id and assigned_by_user_id)
        query = (
            db.query(User)
            .join(AdminPlatform, AdminPlatform.user_id == User.id)
            .filter(AdminPlatform.platform_id == platform_id)
        )

        if not include_inactive:
            query = query.filter(AdminPlatform.is_active == True)

        return query.all()

    def get_admin_assignments(
        self,
        db: Session,
        admin_user_id: int,
    ) -> list[AdminPlatform]:
        """
        Get all platform assignments for an admin with platform details.

        Args:
            db: Database session
            admin_user_id: User ID of the admin

        Returns:
            List of AdminPlatform objects with platform relationship loaded
        """
        return (
            db.query(AdminPlatform)
            .options(joinedload(AdminPlatform.platform))
            .filter(
                AdminPlatform.user_id == admin_user_id,
                AdminPlatform.is_active == True,
            )
            .all()
        )

    # ============================================================================
    # SUPER ADMIN MANAGEMENT
    # ============================================================================

    def toggle_super_admin(
        self,
        db: Session,
        user_id: int,
        is_super_admin: bool,
        current_admin_id: int,
    ) -> User:
        """
        Promote or demote a user to/from super admin.

        When demoting from super admin, the admin will have no platform access
        until explicitly assigned via assign_admin_to_platform.

        Args:
            db: Database session
            user_id: User ID to modify
            is_super_admin: True to promote, False to demote
            current_admin_id: Super admin making the change

        Returns:
            Updated User object

        Raises:
            CannotModifySelfException: If trying to demote self
            ValidationException: If user is not an admin
        """
        if user_id == current_admin_id and not is_super_admin:
            raise CannotModifySelfException(
                user_id=user_id,
                operation="demote from super admin",
            )

        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise UserNotFoundException(str(user_id))
        if not user.is_admin:
            raise AdminOperationException(
                operation="toggle_super_admin",
                reason="User must be an admin to be promoted to super admin",
            )

        user.role = "super_admin" if is_super_admin else "platform_admin"
        user.updated_at = datetime.now(UTC)
        db.flush()
        db.refresh(user)

        action = "promoted to" if is_super_admin else "demoted from"
        logger.info(
            f"User {user.username} {action} super admin by admin {current_admin_id}"
        )

        return user

    def create_platform_admin(
        self,
        db: Session,
        email: str,
        username: str,
        password: str,
        platform_ids: list[int],
        created_by_user_id: int,
        first_name: str | None = None,
        last_name: str | None = None,
    ) -> tuple[User, list[AdminPlatform]]:
        """
        Create a new platform admin with platform assignments.

        Args:
            db: Database session
            email: Admin email
            username: Admin username
            password: Admin password
            platform_ids: List of platform IDs to assign
            created_by_user_id: Super admin creating the account
            first_name: Optional first name
            last_name: Optional last name

        Returns:
            Tuple of (User, list of AdminPlatform assignments)
        """
        from middleware.auth import AuthManager

        auth_manager = AuthManager()

        # Check for existing user
        existing = db.query(User).filter(
            (User.email == email) | (User.username == username)
        ).first()
        if existing:
            field = "email" if existing.email == email else "username"
            raise UserAlreadyExistsException(f"{field.capitalize()} already exists", field=field)

        # Create admin user
        user = User(
            email=email,
            username=username,
            hashed_password=auth_manager.hash_password(password),
            first_name=first_name,
            last_name=last_name,
            role="platform_admin",
            is_active=True,
        )
        db.add(user)
        db.flush()

        # Create platform assignments
        assignments = []
        for platform_id in platform_ids:
            assignment = AdminPlatform(
                user_id=user.id,
                platform_id=platform_id,
                assigned_by_user_id=created_by_user_id,
                is_active=True,
            )
            db.add(assignment)  # noqa: PERF006
            assignments.append(assignment)

        db.flush()
        db.refresh(user)

        logger.info(
            f"Created platform admin {username} with access to platforms "
            f"{platform_ids} by admin {created_by_user_id}"
        )

        return user, assignments


    # ============================================================================
    # ADMIN USER CRUD OPERATIONS
    # ============================================================================

    def list_admin_users(
        self,
        db: Session,
        skip: int = 0,
        limit: int = 100,
        include_super_admins: bool = True,
        is_active: bool | None = None,
        search: str | None = None,
        include_deleted: bool = False,
        only_deleted: bool = False,
    ) -> tuple[list[User], int]:
        """
        List all admin users with optional filtering.

        Args:
            db: Database session
            skip: Number of records to skip
            limit: Maximum records to return
            include_super_admins: Whether to include super admins
            is_active: Filter by active status
            search: Search term for username/email/name
            include_deleted: Include soft-deleted users
            only_deleted: Show only soft-deleted users

        Returns:
            Tuple of (list of User objects, total count)
        """
        query = db.query(User).filter(
            User.role.in_(["super_admin", "platform_admin"])
        )

        # Soft-delete visibility
        if include_deleted or only_deleted:
            query = query.execution_options(include_deleted=True)
        if only_deleted:
            query = query.filter(User.deleted_at.isnot(None))

        if not include_super_admins:
            query = query.filter(User.role == "platform_admin")

        if is_active is not None:
            query = query.filter(User.is_active == is_active)

        if search:
            search_term = f"%{search}%"
            query = query.filter(
                (User.username.ilike(search_term))
                | (User.email.ilike(search_term))
                | (User.first_name.ilike(search_term))
                | (User.last_name.ilike(search_term))
            )

        total = query.count()

        admins = (
            query.options(joinedload(User.admin_platforms))
            .offset(skip)
            .limit(limit)
            .all()
        )

        return admins, total

    def get_admin_user(
        self,
        db: Session,
        user_id: int,
    ) -> User:
        """
        Get a single admin user by ID with platform assignments.

        Args:
            db: Database session
            user_id: User ID

        Returns:
            User object with admin_platforms loaded

        Raises:
            ValidationException: If user not found or not an admin
        """
        admin = (
            db.query(User)
            .options(joinedload(User.admin_platforms))
            .filter(
                User.id == user_id,
                User.role.in_(["super_admin", "platform_admin"]),
            )
            .first()
        )

        if not admin:
            raise UserNotFoundException(str(user_id))

        return admin

    def create_super_admin(
        self,
        db: Session,
        email: str,
        username: str,
        password: str,
        created_by_user_id: int,
        first_name: str | None = None,
        last_name: str | None = None,
    ) -> User:
        """
        Create a new super admin user.

        Args:
            db: Database session
            email: Admin email
            username: Admin username
            password: Admin password
            created_by_user_id: Super admin creating the account
            first_name: Optional first name
            last_name: Optional last name

        Returns:
            Created User object
        """
        from app.core.security import get_password_hash

        # Check for existing user
        existing = (
            db.query(User)
            .filter((User.email == email) | (User.username == username))
            .first()
        )
        if existing:
            field = "email" if existing.email == email else "username"
            raise UserAlreadyExistsException(f"{field.capitalize()} already exists", field=field)

        user = User(
            email=email,
            username=username,
            hashed_password=get_password_hash(password),
            first_name=first_name,
            last_name=last_name,
            role="super_admin",
            is_active=True,
        )
        db.add(user)
        db.flush()
        db.refresh(user)

        logger.info(
            f"Created super admin {username} by admin {created_by_user_id}"
        )

        return user

    def toggle_admin_status(
        self,
        db: Session,
        user_id: int,
        current_admin_id: int,
    ) -> User:
        """
        Toggle admin user active status.

        Args:
            db: Database session
            user_id: User ID to toggle
            current_admin_id: Super admin making the change

        Returns:
            Updated User object

        Raises:
            CannotModifySelfException: If trying to deactivate self
            ValidationException: If user not found or not an admin
        """
        if user_id == current_admin_id:
            raise CannotModifySelfException(
                user_id=user_id,
                operation="deactivate own account",
            )

        admin = db.query(User).filter(
            User.id == user_id,
            User.role.in_(["super_admin", "platform_admin"]),
        ).first()

        if not admin:
            raise UserNotFoundException(str(user_id))

        admin.is_active = not admin.is_active
        admin.updated_at = datetime.now(UTC)
        db.flush()
        db.refresh(admin)

        action = "activated" if admin.is_active else "deactivated"
        logger.info(
            f"Admin {admin.username} {action} by admin {current_admin_id}"
        )

        return admin

    def delete_admin_user(
        self,
        db: Session,
        user_id: int,
        current_admin_id: int,
    ) -> None:
        """
        Delete an admin user and their platform assignments.

        Args:
            db: Database session
            user_id: User ID to delete
            current_admin_id: Super admin making the deletion

        Raises:
            CannotModifySelfException: If trying to delete self
            ValidationException: If user not found or not an admin
        """
        if user_id == current_admin_id:
            raise CannotModifySelfException(
                user_id=user_id,
                operation="delete own account",
            )

        admin = db.query(User).filter(
            User.id == user_id,
            User.role.in_(["super_admin", "platform_admin"]),
        ).first()

        if not admin:
            raise UserNotFoundException(str(user_id))

        username = admin.username

        # Delete admin platform assignments first
        db.query(AdminPlatform).filter(AdminPlatform.user_id == user_id).delete()

        # Delete the admin user
        db.delete(admin)
        db.flush()

        logger.info(f"Admin {username} deleted by admin {current_admin_id}")


# Singleton instance
admin_platform_service = AdminPlatformService()
