"""
Core SDK functionality and base classes
"""

import requests
import time
import logging
from typing import Dict, Any, List, Optional
from urllib.parse import quote

from .models import TallyfyError


class BaseSDK:
    """
    Base SDK class with common functionality for HTTP requests and error handling
    """

    def __init__(self, api_key: str, base_url: str = "https://api.tallyfy.com", timeout: int = 30, max_retries: int = 3, retry_delay: float = 1.0):
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout
        self.max_retries = max_retries
        self.retry_delay = retry_delay
        
        # Setup default headers
        self.default_headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-Tallyfy-Client': 'TallyfySDK',
            'Authorization': f'Bearer {api_key}'
        }
        
        # Setup logging
        self.logger = logging.getLogger(__name__)
        
        # Setup session for connection pooling
        self.session = requests.Session()
        self.session.headers.update(self.default_headers)

    def _build_url(self, endpoint: str) -> str:
        """Build full URL from endpoint, URL-encoding each path segment."""
        endpoint = endpoint.lstrip('/')
        segments = endpoint.split('/')
        encoded = '/'.join(quote(seg, safe='') for seg in segments)
        return f"{self.base_url}/{encoded}"

    def _make_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None, data: Optional[Dict] = None, form_data: Optional[Dict] = None) -> Dict[str, Any]:
        """
        Make HTTP request with retry logic

        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: API endpoint
            params: URL parameters
            data: Request body data (sent as JSON)
            form_data: Request body data (sent as application/x-www-form-urlencoded)

        Returns:
            Parsed JSON response data

        Raises:
            TallyfyError: If request fails after all retries
        """
        url = self._build_url(endpoint)

        # Prepare request arguments
        request_args = {
            'timeout': self.timeout,
            'params': params
        }

        if form_data:
            request_args['data'] = form_data
            request_args['headers'] = {'Content-Type': 'application/x-www-form-urlencoded'}
        elif data:
            request_args['json'] = data
        
        # Retry logic
        last_exception = None
        
        for attempt in range(self.max_retries + 1):
            try:
                self.logger.debug(f"Making {method} request to {url} (attempt {attempt + 1})")
                response = self.session.request(method, url, **request_args)
                
                # Handle 204 No Content (e.g. DELETE responses)
                if response.status_code == 204:
                    return {'success': True}

                # Parse response
                try:
                    response_data = response.json()
                except ValueError:
                    response_data = response.text
                
                # Check if request was successful
                if response.ok:
                    return response_data
                else:
                    error_msg = f"API request failed with status {response.status_code}"
                    if isinstance(response_data, dict) and 'message' in response_data:
                        error_msg += f": {response_data['message']}"
                    elif isinstance(response_data, str):
                        error_msg += f": {response_data}"
                    
                    # Don't retry on client errors (4xx)
                    if 400 <= response.status_code < 500:
                        raise TallyfyError(
                            error_msg,
                            status_code=response.status_code,
                            response_data=response_data
                        )
                    
                    # Retry on server errors (5xx)
                    if attempt < self.max_retries:
                        self.logger.warning(f"Request failed, retrying in {self.retry_delay}s...")
                        time.sleep(self.retry_delay)
                        continue
                    else:
                        raise TallyfyError(
                            error_msg,
                            status_code=response.status_code,
                            response_data=response_data
                        )
                        
            except requests.exceptions.RequestException as req_error:
                last_exception = req_error
                if attempt < self.max_retries:
                    self.logger.warning(f"Request failed with {type(req_error).__name__}, retrying in {self.retry_delay}s...")
                    time.sleep(self.retry_delay)
                    continue
                else:
                    break
        
        # If we get here, all retries failed
        raise TallyfyError(f"Request failed after {self.max_retries + 1} attempts: {str(last_exception)}")

    def close(self):
        """Close the HTTP session and cleanup resources"""
        if hasattr(self, 'session'):
            self.session.close()

    def __enter__(self):
        """Context manager entry"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit"""
        self.close()


class TallyfySDK(BaseSDK):
    """
    High-level SDK for Tallyfy API endpoints
    Provides typed methods for interacting with Tallyfy's REST API
    """

    def __init__(self, api_key: str, base_url: str = "https://api.tallyfy.com", timeout: int = 30, max_retries: int = 3, retry_delay: float = 1.0):
        super().__init__(api_key, base_url, timeout, max_retries, retry_delay)
        
        # Initialize management modules
        from .user_management import UserManager
        from .task_management import TaskManager
        from .template_management import TemplateManager
        from .form_fields_management import FormFieldManager
        from .organization_management import OrganizationManager
        from .thread_management import ThreadManager
        from .group_management import GroupManager
        from .tag_management import TagManager
        from .folder_management import FolderManager

        self.users = UserManager(self)
        self.tasks = TaskManager(self)
        self.templates = TemplateManager(self)
        self.form_fields = FormFieldManager(self)
        self.organizations = OrganizationManager(self)
        self.threads = ThreadManager(self)
        self.groups = GroupManager(self)
        self.tags = TagManager(self)
        self.folders = FolderManager(self)

    def get_current_user_info(self, org_id: str):
        """Get current user information."""
        return self.users.get_current_user_info(org_id)

    def get_user(self, org_id: str, user_id: int):
        """Get user information."""
        return self.users.get_user(org_id, user_id)
    
    def get_current_user_organizations(self, page: int = 1, per_page: int = 10):
        """Get all organizations the current member is a part of."""
        return self.organizations.get_current_user_organizations(page, per_page)
    # Backward compatibility methods - delegate to management modules
    def get_organization_users(self, org_id: str, with_groups: bool = False):
        """Get all organization members with full profile data."""
        return self.users.get_organization_users(org_id, with_groups)
    
    def get_organization_users_list(self, org_id: str):
        """Get organization members with minimal data."""
        return self.users.get_organization_users_list(org_id)
    
    def get_organization_guests(self, org_id: str, with_stats: bool = False):
        """Get organization guests with full profile data."""
        return self.users.get_organization_guests(org_id, with_stats)
    
    def get_organization_guests_list(self, org_id: str):
        """Get organization guests with minimal data."""
        return self.users.get_organization_guests_list(org_id)
    
    def invite_user_to_organization(self, org_id: str, email: str, first_name: str, last_name: str, role: str = "light", message: Optional[str] = None):
        """Invite a member to your organization."""
        return self.users.invite_user_to_organization(org_id, email, first_name, last_name, role, message)
    
    def get_my_tasks(self, org_id: str):
        """Get tasks assigned to the current user."""
        return self.tasks.get_my_tasks(org_id)
    
    def get_user_tasks(self, org_id: str, user_id: int):
        """Get tasks assigned to a specific user."""
        return self.tasks.get_user_tasks(org_id, user_id)
    
    def get_tasks_for_process(self, org_id: str, process_id: Optional[str] = None, process_name: Optional[str] = None,
                           status: Optional[str] = None, sort: Optional[str] = None, with_: Optional[str] = None,
                           owners: Optional[str] = None, groups: Optional[str] = None, guests: Optional[str] = None,
                           page: Optional[int] = None, per_page: Optional[int] = None, current_task: Optional[str] = None,
                           replace_page: Optional[int] = None, without_pagination: str = "true",
                           deadline_start_range: Optional[str] = None, deadline_end_range: Optional[str] = None,
                           unassigned: Optional[bool] = None):
        """Get all tasks for a given process."""
        return self.tasks.get_tasks_for_process(org_id, process_id, process_name,
                           status, sort, with_,
                           owners, groups, guests,
                           page, per_page, current_task,
                           replace_page, without_pagination,
                           deadline_start_range, deadline_end_range,
                           unassigned)
    
    def get_organization_runs(self, org_id: str, per_page: int = 25, page: int = 1, with_data: Optional[str] = None,
                            form_fields_values: Optional[bool] = None, owners: Optional[str] = None,
                            task_status: Optional[str] = None, groups: Optional[str] = None,
                            status: Optional[str] = None, folder: Optional[str] = None,
                            checklist_id: Optional[str] = None, starred: Optional[bool] = None,
                            run_type: Optional[str] = None, tag: Optional[str] = None, sort: Optional[str] = None,
                            archived: Optional[str] = None):
        """Get all processes (runs) in the organization."""
        return self.tasks.get_organization_runs(org_id, per_page, page, with_data, form_fields_values, owners, task_status, groups, status, folder, checklist_id, starred, run_type, tag, sort, archived=archived)
    
    def launch_process(self, org_id: str, template_id: str, name: str,
                       summary: Optional[str] = None, owner_id: Optional[int] = None,
                       is_public: Optional[bool] = None, assign_someone_else: Optional[bool] = None,
                       publicly_hidden_fields: Optional[list] = None, prerun: Optional[list] = None,
                       tasks: Optional[Dict] = None, tags: Optional[list] = None,
                       users: Optional[list] = None, groups: Optional[list] = None,
                       roles: Optional[list] = None, folders: Optional[list] = None,
                       parent_id: Optional[str] = None):
        """Launch a new process (run) from a template."""
        return self.tasks.launch_process(
            org_id, template_id, name,
            summary=summary, owner_id=owner_id, is_public=is_public,
            assign_someone_else=assign_someone_else, publicly_hidden_fields=publicly_hidden_fields,
            prerun=prerun, tasks=tasks, tags=tags, users=users, groups=groups,
            roles=roles, folders=folders, parent_id=parent_id,
        )

    def get_groups(self, org_id: str, page: int = 1, per_page: int = 100,
                   q: Optional[str] = None):
        """List all groups in the organization."""
        return self.groups.get_groups(org_id, page=page, per_page=per_page, q=q)

    def get_group(self, org_id: str, group_id: str):
        """Get a single group by ID."""
        return self.groups.get_group(org_id, group_id)

    def create_group(self, org_id: str, name: str, description: Optional[str] = None,
                     members: Optional[list] = None, guests: Optional[list] = None):
        """Create a new group."""
        return self.groups.create_group(org_id, name, description=description,
                                        members=members, guests=guests)

    def update_group(self, org_id: str, group_id: str, name: Optional[str] = None,
                     description: Optional[str] = None, members: Optional[list] = None,
                     guests: Optional[list] = None):
        """Update a group's properties."""
        return self.groups.update_group(org_id, group_id, name=name,
                                        description=description, members=members, guests=guests)

    def delete_group(self, org_id: str, group_id: str):
        """Delete a group."""
        return self.groups.delete_group(org_id, group_id)

    # ── Tag convenience methods ────────────────────────────────────────────

    def get_tags(self, org_id: str, page: int = 1, per_page: int = 100,
                 q: Optional[str] = None):
        """List all tags in the organization."""
        return self.tags.get_tags(org_id, page=page, per_page=per_page, q=q)

    def get_tag(self, org_id: str, tag_id: str):
        """Get a single tag by ID."""
        return self.tags.get_tag(org_id, tag_id)

    def create_tag(self, org_id: str, title: str, color: Optional[str] = None):
        """Create a new tag."""
        return self.tags.create_tag(org_id, title, color=color)

    def update_tag(self, org_id: str, tag_id: str, title: Optional[str] = None,
                   color: Optional[str] = None):
        """Update a tag's properties."""
        return self.tags.update_tag(org_id, tag_id, title=title, color=color)

    def delete_tag(self, org_id: str, tag_id: str):
        """Delete a tag."""
        return self.tags.delete_tag(org_id, tag_id)

    # ── Folder convenience methods ─────────────────────────────────────────

    def get_folders(self, org_id: str, page: int = 1, per_page: int = 100,
                    q: Optional[str] = None):
        """List all folders in the organization."""
        return self.folders.get_folders(org_id, page=page, per_page=per_page, q=q)

    def get_folder(self, org_id: str, folder_id: str):
        """Get a single folder by ID."""
        return self.folders.get_folder(org_id, folder_id)

    def create_folder(self, org_id: str, name: str, parent_id: Optional[str] = None):
        """Create a new folder."""
        return self.folders.create_folder(org_id, name, parent_id=parent_id)

    def update_folder(self, org_id: str, folder_id: str, name: Optional[str] = None,
                      parent_id: Optional[str] = None):
        """Update a folder's properties."""
        return self.folders.update_folder(org_id, folder_id, name=name, parent_id=parent_id)

    def delete_folder(self, org_id: str, folder_id: str):
        """Delete a folder."""
        return self.folders.delete_folder(org_id, folder_id)

    def add_task_comment(self, org_id: str, task_id: str, content: str,
                         state: Optional[str] = None, label: Optional[str] = None):
        """Post a new comment on a task."""
        return self.threads.add_task_comment(org_id, task_id, content, state=state, label=label)

    def get_task_comments(self, org_id: str, run_id: str, task_id: str):
        """Get all comments on a task."""
        return self.threads.get_task_comments(org_id, run_id, task_id)

    def update_task_comment(self, org_id: str, task_id: str, comment_id: str, content: str,
                            state: Optional[str] = None, label: Optional[str] = None):
        """Update an existing comment on a task."""
        return self.threads.update_task_comment(org_id, task_id, comment_id, content,
                                                state=state, label=label)

    def delete_task_comment(self, org_id: str, task_id: str, comment_id: str):
        """Delete a comment on a task."""
        return self.threads.delete_task_comment(org_id, task_id, comment_id)

    def complete_task(self, org_id: str, run_id: str, task_id: str,
                      is_approved: Optional[bool] = None, override_user: Optional[int] = None):
        """Mark a process task as complete."""
        return self.tasks.complete_task(org_id, run_id, task_id, is_approved=is_approved, override_user=override_user)

    def reopen_task(self, org_id: str, run_id: str, task_id: str):
        """Reopen a previously completed task."""
        return self.tasks.reopen_task(org_id, run_id, task_id)

    def update_task(self, org_id: str, run_id: str, task_id: str, **kwargs):
        """Update properties of a task within a process run."""
        return self.tasks.update_task(org_id, run_id, task_id, **kwargs)

    def get_process(self, org_id: str, run_id: str, with_: Optional[str] = None):
        """Get full details of a single process by ID."""
        return self.tasks.get_process(org_id, run_id, with_=with_)

    def update_process(self, org_id: str, run_id: str, name: Optional[str] = None,
                       summary: Optional[str] = None, starred: Optional[bool] = None):
        """Update process properties (name, summary, starred)."""
        return self.tasks.update_process(org_id, run_id, name=name, summary=summary, starred=starred)

    def archive_process(self, org_id: str, run_id: str):
        """Archive (soft-delete) a process run."""
        return self.tasks.archive_process(org_id, run_id)

    def create_task(self, org_id: str, title: str, deadline: str, description: Optional[str] = None, owners = None, max_assignable: Optional[int] = None, prevent_guest_comment: Optional[bool] = None):
        """Create a standalone task."""
        return self.tasks.create_task(org_id=org_id, title=title, deadline=deadline, description=description, owners=owners, max_assignable=max_assignable, prevent_guest_comment=prevent_guest_comment)
    
    def search_processes_by_name(self, org_id: str, process_name: str):
        """Search processes by name."""
        return self.tasks.search_processes_by_name(org_id, process_name)
    
    def search_templates_by_name(self, org_id: str, template_name: str):
        """Search templates by name."""
        return self.templates.search_templates_by_name(org_id, template_name)
    
    def search(self, org_id: str, search_query: str, search_type: str = "process", per_page: int = 20):
        """Universal search for processes, templates, or tasks."""
        return self.tasks.search(org_id, search_query, search_type, per_page)
    
    def get_template(self, org_id: str, template_id: Optional[str] = None, template_name: Optional[str] = None):
        """Get template by ID or name."""
        return self.templates.get_template(org_id, template_id, template_name)

    def get_all_templates(self, org_id: str, per_page: int = 100):
        """Get all templates by organization."""
        return self.templates.get_all_templates(org_id, per_page)

    def update_template_metadata(self, org_id: str, template_id: str, **kwargs):
        """Update template metadata."""
        return self.templates.update_template_metadata(org_id, template_id, **kwargs)
    
    def get_template_with_steps(self, org_id: str, template_id: Optional[str] = None, template_name: Optional[str] = None):
        """Get template with steps."""
        return self.templates.get_template_with_steps(org_id, template_id, template_name)
    
    def duplicate_template(self, org_id: str, template_id: str, new_name: str, tenant: str):
        """Duplicate template."""
        return self.templates.duplicate_template(org_id, template_id, new_name, tenant)
    
    def get_template_steps(self, org_id: str, template_id: str):
        """Get template steps."""
        return self.templates.get_template_steps(org_id, template_id)
    
    def get_step_dependencies(self, org_id: str, template_id: str, step_id: str):
        """Analyze step dependencies."""
        return self.templates.get_step_dependencies(org_id, template_id, step_id)
    
    # Form field methods
    def add_form_field_to_step(self, org_id: str, template_id: str, step_id: str, field_data: Dict[str, Any]):
        """Add form field to step."""
        return self.form_fields.add_form_field_to_step(org_id, template_id, step_id, field_data)
    
    def update_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str, **kwargs):
        """Update form field."""
        return self.form_fields.update_form_field(org_id, template_id, step_id, field_id, **kwargs)
    
    def move_form_field(self, org_id: str, template_id: str, from_step: str, field_id: str, to_step: str, position: int = 1):
        """Move form field between steps."""
        return self.form_fields.move_form_field(org_id, template_id, from_step, field_id, to_step, position)
    
    def delete_form_field(self, org_id: str, template_id: str, step_id: str, field_id: str):
        """Delete form field."""
        return self.form_fields.delete_form_field(org_id, template_id, step_id, field_id)
    
    # Automation management methods
    def create_automation_rule(self, org_id: str, template_id: str, automation_data: Dict[str, Any]):
        """Create conditional automation (if-then rules)."""
        return self.templates.create_automation_rule(org_id, template_id, automation_data)
    
    def update_automation_rule(self, org_id: str, template_id: str, automation_id: str, update_data: dict = None, **kwargs):
        """Modify automation conditions and actions."""
        if update_data is None:
            update_data = kwargs
        return self.templates.update_automation_rule(org_id, template_id, automation_id, update_data)
    
    def delete_automation_rule(self, org_id: str, template_id: str, automation_id: str):
        """Remove an automation rule."""
        return self.templates.delete_automation_rule(org_id, template_id, automation_id)
    
    def get_step_visibility_conditions(self, org_id: str, template_id: str, step_id: str):
        """Analyze when/how a step becomes visible based on all automations."""
        return self.templates.get_step_visibility_conditions(org_id, template_id, step_id)
    
    def get_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str):
        """Get dropdown options."""
        return self.form_fields.get_dropdown_options(org_id, template_id, step_id, field_id)
    
    def update_dropdown_options(self, org_id: str, template_id: str, step_id: str, field_id: str, options):
        """Update dropdown options."""
        return self.form_fields.update_dropdown_options(org_id, template_id, step_id, field_id, options)
    
    def add_assignees_to_step(self, org_id: str, template_id: str, step_id: str, assignees: Dict[str, Any], guests: Optional[List[str]] = None):
        """Add assignees to a specific step in a template."""
        return self.templates.add_assignees_to_step(org_id, template_id, step_id, assignees, guests=guests)
    
    def edit_description_on_step(self, org_id: str, template_id: str, step_id: str, description: str):
        """Edit the description/summary of a specific step in a template."""
        return self.templates.edit_description_on_step(org_id, template_id, step_id, description)
    
    def add_step_to_template(self, org_id: str, template_id: str, step_data: Dict[str, Any]):
        """Add a new step to a template."""
        return self.templates.add_step_to_template(org_id, template_id, step_data)

    def create_template(self, org_id: str, title: str, type: str = 'procedure', **kwargs):
        """Create a new template."""
        return self.templates.create_template(org_id, title, type, **kwargs)

    def delete_template(self, org_id: str, template_id: str):
        """Delete (soft-delete) a template."""
        return self.templates.delete_template(org_id, template_id)

    def delete_step(self, org_id: str, template_id: str, step_id: str):
        """Delete a step from a template."""
        return self.templates.delete_step(org_id, template_id, step_id)

    def clone_step(self, org_id: str, template_id: str, step_id: str):
        """Clone a step within a template."""
        return self.templates.clone_step(org_id, template_id, step_id)

    def reorder_step(self, org_id: str, template_id: str, step_id: str, position: int):
        """Reorder a step within a template."""
        return self.templates.reorder_step(org_id, template_id, step_id, position)

    def get_kickoff_form_fields(self, org_id: str, template_id: str):
        """Get kick-off form fields for a template."""
        return self.templates.get_kickoff_form_fields(org_id, template_id)

    def get_task(self, org_id: str, run_id: str, task_id: str, with_: Optional[str] = None):
        """Get a single task within a process run."""
        return self.tasks.get_task(org_id, run_id, task_id, with_=with_)

    def get_standalone_task(self, org_id: str, task_id: str):
        """Get a standalone (one-off) task."""
        return self.tasks.get_standalone_task(org_id, task_id)

    def update_standalone_task(self, org_id: str, task_id: str, **kwargs):
        """Update a standalone (one-off) task."""
        return self.tasks.update_standalone_task(org_id, task_id, **kwargs)

    def delete_standalone_task(self, org_id: str, task_id: str):
        """Delete a standalone (one-off) task."""
        return self.tasks.delete_standalone_task(org_id, task_id)

    def reactivate_process(self, org_id: str, run_id: str):
        """Reactivate an archived process."""
        return self.tasks.reactivate_process(org_id, run_id)

    def complete_kickoff_form(self, org_id: str, run_id: str):
        """Complete the kick-off form for a process."""
        return self.tasks.complete_kickoff_form(org_id, run_id)

    def reopen_kickoff_form(self, org_id: str, run_id: str):
        """Reopen a previously completed kick-off form."""
        return self.tasks.reopen_kickoff_form(org_id, run_id)

    def report_task_issue(self, org_id: str, task_id: str, content: str):
        """Report an issue on a task."""
        return self.threads.report_task_issue(org_id, task_id, content)

    def resolve_task_issues(self, org_id: str, task_id: str):
        """Resolve all open issues on a task."""
        return self.threads.resolve_task_issues(org_id, task_id)

    def get_organization(self, org_id: str, with_: Optional[str] = None):
        """Get details of a single organization."""
        return self.organizations.get_organization(org_id, with_=with_)

    def reorder_form_fields(self, org_id: str, template_id: str, step_id: str, field_order: list):
        """Reorder form fields within a step."""
        return self.form_fields.reorder_form_fields(org_id, template_id, step_id, field_order)

    def add_object_to_folder(self, org_id: str, folder_id: str, object_id: str, object_type: str):
        """Add an object to a folder."""
        return self.folders.add_object_to_folder(org_id, folder_id, object_id, object_type)

    def remove_object_from_folder(self, org_id: str, folder_object_id: str):
        """Remove an object from a folder."""
        return self.folders.remove_object_from_folder(org_id, folder_object_id)

    def tag_template(self, org_id: str, template_id: str, tag_id: str):
        """Attach a tag to a template."""
        return self.tags.tag_template(org_id, template_id, tag_id)

    def untag_template(self, org_id: str, tag_id: str, template_id: str = None):
        """Remove a tag from a template."""
        return self.tags.untag_template(org_id, tag_id, template_id=template_id)

    def tag_process(self, org_id: str, process_id: str, tag_id: str):
        """Attach a tag to a process run."""
        return self.tags.tag_process(org_id, process_id, tag_id)

    def untag_process(self, org_id: str, process_id: str, tag_id: str):
        """Remove a tag from a process run."""
        return self.tags.untag_process(org_id, process_id, tag_id)

    # ── Guest management methods ──────────────────────────────────────────

    def create_guest(self, org_id: str, email: str, first_name: str, last_name: str, **kwargs):
        """Create a new guest in the organization."""
        return self.users.create_guest(org_id, email, first_name, last_name, **kwargs)

    def get_guest(self, org_id: str, email: str):
        """Get a guest by email."""
        return self.users.get_guest(org_id, email)

    def update_guest(self, org_id: str, email: str, **kwargs):
        """Update a guest's profile."""
        return self.users.update_guest(org_id, email, **kwargs)

    def disable_guest(self, org_id: str, email: str):
        """Disable a guest."""
        return self.users.disable_guest(org_id, email)

    def enable_guest(self, org_id: str, email: str):
        """Re-enable a guest."""
        return self.users.enable_guest(org_id, email)

    def get_guest_tasks(self, org_id: str, guest_id: str, **kwargs):
        """Get tasks assigned to a specific guest."""
        return self.users.get_guest_tasks(org_id, guest_id, **kwargs)

    # ── Admin user management methods ─────────────────────────────────────

    def change_user_role(self, org_id: str, user_id: int, role: str):
        """Change a user's role."""
        return self.users.change_user_role(org_id, user_id, role)

    def disable_user(self, org_id: str, user_id: int):
        """Disable a user."""
        return self.users.disable_user(org_id, user_id)

    def enable_user(self, org_id: str, user_id: int):
        """Re-enable a user."""
        return self.users.enable_user(org_id, user_id)

    def approve_user(self, org_id: str, user_id: int):
        """Approve a pending user."""
        return self.users.approve_user(org_id, user_id)