# app/modules/tenancy/services/vendor_team_service.py
"""
Vendor team management service.

Handles:
- Team member invitations
- Invitation acceptance
- Role assignment
- Permission management
"""

import logging
import secrets
from datetime import datetime, timedelta
from typing import Any

from sqlalchemy.orm import Session

from app.modules.tenancy.services.permission_discovery_service import (
    permission_discovery_service,
)


def get_preset_permissions(preset_name: str) -> set[str]:
    """Get permissions for a preset role."""
    return permission_discovery_service.get_preset_permissions(preset_name)
from app.modules.tenancy.exceptions import (
    CannotRemoveOwnerException,
    InvalidInvitationTokenException,
    TeamInvitationAlreadyAcceptedException,
    TeamMemberAlreadyExistsException,
    UserNotFoundException,
)
from app.modules.billing.exceptions import TierLimitExceededException
from middleware.auth import AuthManager
from app.modules.tenancy.models import User
from app.modules.tenancy.models import Role, Vendor, VendorUser, VendorUserType

logger = logging.getLogger(__name__)


class VendorTeamService:
    """Service for managing vendor team members."""

    def __init__(self):
        self.auth_manager = AuthManager()

    def invite_team_member(
        self,
        db: Session,
        vendor: Vendor,
        inviter: User,
        email: str,
        role_name: str,
        custom_permissions: list[str] | None = None,
    ) -> dict[str, Any]:
        """
        Invite a new team member to a vendor.

        Creates:
        1. User account (if doesn't exist)
        2. Role (if custom permissions provided)
        3. VendorUser relationship with invitation token

        Args:
            db: Database session
            vendor: Vendor to invite to
            inviter: User sending the invitation
            email: Email of person to invite
            role_name: Role name (manager, staff, support, etc.)
            custom_permissions: Optional custom permissions (overrides preset)

        Returns:
            Dict with invitation details
        """
        try:
            # Check team size limit from subscription
            from app.modules.billing.services import subscription_service

            subscription_service.check_team_limit(db, vendor.id)

            # Check if user already exists
            user = db.query(User).filter(User.email == email).first()

            if user:
                # Check if already a member
                existing_membership = (
                    db.query(VendorUser)
                    .filter(
                        VendorUser.vendor_id == vendor.id,
                        VendorUser.user_id == user.id,
                    )
                    .first()
                )

                if existing_membership:
                    if existing_membership.is_active:
                        raise TeamMemberAlreadyExistsException(
                            email, vendor.vendor_code
                        )
                    # Reactivate old membership
                    existing_membership.is_active = (
                        False  # Will be activated on acceptance
                    )
                    existing_membership.invitation_token = (
                        self._generate_invitation_token()
                    )
                    existing_membership.invitation_sent_at = datetime.utcnow()
                    existing_membership.invitation_accepted_at = None
                    db.flush()

                    logger.info(
                        f"Re-invited user {email} to vendor {vendor.vendor_code}"
                    )
                    return {
                        "invitation_token": existing_membership.invitation_token,
                        "email": email,
                        "existing_user": True,
                    }
            else:
                # Create new user account (inactive until invitation accepted)
                username = email.split("@")[0]
                # Ensure unique username
                base_username = username
                counter = 1
                while db.query(User).filter(User.username == username).first():
                    username = f"{base_username}{counter}"
                    counter += 1

                # Generate temporary password (user will set real one on activation)
                temp_password = secrets.token_urlsafe(16)

                user = User(
                    email=email,
                    username=username,
                    hashed_password=self.auth_manager.hash_password(temp_password),
                    role="vendor",  # Platform role
                    is_active=False,  # Will be activated when invitation accepted
                    is_email_verified=False,
                )
                db.add(user)
                db.flush()  # Get user.id

                logger.info(f"Created new user account for invitation: {email}")

            # Get or create role
            role = self._get_or_create_role(
                db=db,
                vendor=vendor,
                role_name=role_name,
                custom_permissions=custom_permissions,
            )

            # Create vendor membership with invitation
            invitation_token = self._generate_invitation_token()

            vendor_user = VendorUser(
                vendor_id=vendor.id,
                user_id=user.id,
                user_type=VendorUserType.TEAM_MEMBER.value,
                role_id=role.id,
                invited_by=inviter.id,
                invitation_token=invitation_token,
                invitation_sent_at=datetime.utcnow(),
                is_active=False,  # Will be activated on acceptance
            )
            db.add(vendor_user)
            db.flush()

            logger.info(
                f"Invited {email} to vendor {vendor.vendor_code} "
                f"as {role_name} by {inviter.username}"
            )

            # TODO: Send invitation email
            # self._send_invitation_email(email, vendor, invitation_token)

            return {
                "invitation_token": invitation_token,
                "email": email,
                "role": role_name,
                "existing_user": user.is_active,
            }

        except (TeamMemberAlreadyExistsException, TierLimitExceededException):
            raise
        except Exception as e:
            logger.error(f"Error inviting team member: {str(e)}")
            raise

    def accept_invitation(
        self,
        db: Session,
        invitation_token: str,
        password: str,
        first_name: str | None = None,
        last_name: str | None = None,
    ) -> dict[str, Any]:
        """
        Accept a team invitation and activate account.

        Args:
            db: Database session
            invitation_token: Invitation token from email
            password: New password to set
            first_name: Optional first name
            last_name: Optional last name

        Returns:
            Dict with user and vendor info
        """
        try:
            # Find invitation
            vendor_user = (
                db.query(VendorUser)
                .filter(
                    VendorUser.invitation_token == invitation_token,
                )
                .first()
            )

            if not vendor_user:
                raise InvalidInvitationTokenException()

            # Check if already accepted
            if vendor_user.invitation_accepted_at is not None:
                raise TeamInvitationAlreadyAcceptedException()

            # Check token expiration (7 days)
            if vendor_user.invitation_sent_at:
                expiry_date = vendor_user.invitation_sent_at + timedelta(days=7)
                if datetime.utcnow() > expiry_date:
                    raise InvalidInvitationTokenException("Invitation has expired")

            user = vendor_user.user
            vendor = vendor_user.vendor

            # Update user
            user.hashed_password = self.auth_manager.hash_password(password)
            user.is_active = True
            user.is_email_verified = True
            if first_name:
                user.first_name = first_name
            if last_name:
                user.last_name = last_name

            # Activate membership
            vendor_user.is_active = True
            vendor_user.invitation_accepted_at = datetime.utcnow()
            vendor_user.invitation_token = None  # Clear token

            db.flush()

            logger.info(
                f"User {user.email} accepted invitation to vendor {vendor.vendor_code}"
            )

            return {
                "user": user,
                "vendor": vendor,
                "role": vendor_user.role.name if vendor_user.role else "member",
            }

        except (
            InvalidInvitationTokenException,
            TeamInvitationAlreadyAcceptedException,
        ):
            raise
        except Exception as e:
            logger.error(f"Error accepting invitation: {str(e)}")
            raise

    def remove_team_member(
        self,
        db: Session,
        vendor: Vendor,
        user_id: int,
    ) -> bool:
        """
        Remove a team member from a vendor.

        Cannot remove owner.

        Args:
            db: Database session
            vendor: Vendor to remove from
            user_id: User ID to remove

        Returns:
            True if removed
        """
        try:
            vendor_user = (
                db.query(VendorUser)
                .filter(
                    VendorUser.vendor_id == vendor.id,
                    VendorUser.user_id == user_id,
                )
                .first()
            )

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

            # Cannot remove owner
            if vendor_user.is_owner:
                raise CannotRemoveOwnerException(user_id, vendor.id)

            # Soft delete - just deactivate
            vendor_user.is_active = False

            logger.info(f"Removed user {user_id} from vendor {vendor.vendor_code}")
            return True

        except (UserNotFoundException, CannotRemoveOwnerException):
            raise
        except Exception as e:
            logger.error(f"Error removing team member: {str(e)}")
            raise

    def update_member_role(
        self,
        db: Session,
        vendor: Vendor,
        user_id: int,
        new_role_name: str,
        custom_permissions: list[str] | None = None,
    ) -> VendorUser:
        """
        Update a team member's role.

        Args:
            db: Database session
            vendor: Vendor
            user_id: User ID
            new_role_name: New role name
            custom_permissions: Optional custom permissions

        Returns:
            Updated VendorUser
        """
        try:
            vendor_user = (
                db.query(VendorUser)
                .filter(
                    VendorUser.vendor_id == vendor.id,
                    VendorUser.user_id == user_id,
                )
                .first()
            )

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

            # Cannot change owner's role
            if vendor_user.is_owner:
                raise CannotRemoveOwnerException(user_id, vendor.id)

            # Get or create new role
            new_role = self._get_or_create_role(
                db=db,
                vendor=vendor,
                role_name=new_role_name,
                custom_permissions=custom_permissions,
            )

            vendor_user.role_id = new_role.id
            db.flush()

            logger.info(
                f"Updated role for user {user_id} in vendor {vendor.vendor_code} "
                f"to {new_role_name}"
            )

            return vendor_user

        except (UserNotFoundException, CannotRemoveOwnerException):
            raise
        except Exception as e:
            logger.error(f"Error updating member role: {str(e)}")
            raise

    def get_team_members(
        self,
        db: Session,
        vendor: Vendor,
        include_inactive: bool = False,
    ) -> list[dict[str, Any]]:
        """
        Get all team members for a vendor.

        Args:
            db: Database session
            vendor: Vendor
            include_inactive: Include inactive members

        Returns:
            List of team member info
        """
        query = db.query(VendorUser).filter(
            VendorUser.vendor_id == vendor.id,
        )

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

        vendor_users = query.all()

        members = []
        for vu in vendor_users:
            members.append(
                {
                    "id": vu.user.id,
                    "email": vu.user.email,
                    "username": vu.user.username,
                    "first_name": vu.user.first_name,
                    "last_name": vu.user.last_name,
                    "full_name": vu.user.full_name,
                    "user_type": vu.user_type,
                    "role_name": vu.role.name if vu.role else "owner",
                    "role_id": vu.role.id if vu.role else None,
                    "permissions": vu.get_all_permissions(),
                    "is_active": vu.is_active,
                    "is_owner": vu.is_owner,
                    "invitation_pending": vu.is_invitation_pending,
                    "invited_at": vu.invitation_sent_at,
                    "accepted_at": vu.invitation_accepted_at,
                    "joined_at": vu.invitation_accepted_at or vu.created_at or vu.user.created_at,
                }
            )

        return members

    def get_vendor_roles(self, db: Session, vendor_id: int) -> list[dict[str, Any]]:
        """
        Get all roles for a vendor.

        Creates default preset roles if none exist.

        Args:
            db: Database session
            vendor_id: Vendor ID

        Returns:
            List of role info dicts
        """
        roles = db.query(Role).filter(Role.vendor_id == vendor_id).all()

        # Create default roles if none exist
        if not roles:
            default_role_names = ["manager", "staff", "support", "viewer", "marketing"]
            for role_name in default_role_names:
                permissions = list(get_preset_permissions(role_name))
                role = Role(
                    vendor_id=vendor_id,
                    name=role_name,
                    permissions=permissions,
                )
                db.add(role)
            db.flush()  # Flush to get IDs without committing (endpoint commits)
            roles = db.query(Role).filter(Role.vendor_id == vendor_id).all()

        return [
            {
                "id": role.id,
                "name": role.name,
                "permissions": role.permissions or [],
                "vendor_id": role.vendor_id,
                "created_at": role.created_at,
                "updated_at": role.updated_at,
            }
            for role in roles
        ]

    # Private helper methods

    def _generate_invitation_token(self) -> str:
        """Generate a secure invitation token."""
        return secrets.token_urlsafe(32)

    def _get_or_create_role(
        self,
        db: Session,
        vendor: Vendor,
        role_name: str,
        custom_permissions: list[str] | None = None,
    ) -> Role:
        """Get existing role or create new one with preset/custom permissions."""
        # Try to find existing role with same name
        role = (
            db.query(Role)
            .filter(
                Role.vendor_id == vendor.id,
                Role.name == role_name,
            )
            .first()
        )

        if role and custom_permissions is None:
            # Use existing role
            return role

        # Determine permissions
        if custom_permissions:
            permissions = custom_permissions
        else:
            # Get preset permissions
            permissions = list(get_preset_permissions(role_name))

        if role:
            # Update existing role with new permissions
            role.permissions = permissions
        else:
            # Create new role
            role = Role(
                vendor_id=vendor.id,
                name=role_name,
                permissions=permissions,
            )
            db.add(role)

        db.flush()
        return role

    def _send_invitation_email(self, email: str, vendor: Vendor, token: str):
        """Send invitation email (TODO: implement)."""
        # TODO: Implement email sending
        # Should include:
        # - Link to accept invitation: /vendor/invitation/accept?token={token}
        # - Vendor name
        # - Inviter name
        # - Expiry date


# Create service instance
vendor_team_service = VendorTeamService()
