"""
Task creation operations
"""

from typing import Dict, Any, Optional
from .base import TaskManagerBase
from ..models import Task, TaskOwners, Run, TallyfyError
from email_validator import validate_email, EmailNotValidError


class TaskCreation(TaskManagerBase):
    """Handles task creation operations"""

    def create_task(self, org_id: str, title: str, deadline: str,
                   owners: TaskOwners, description: Optional[str] = None,
                   max_assignable: Optional[int] = None, prevent_guest_comment: Optional[bool] = None) -> Optional[Task]:
        """
        Create a standalone task in the organization.

        Args:
            org_id: Organization ID
            title: Task name (required)
            deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
            owners: TaskOwners object with users, guests, and groups
            description: Task description (optional)
            max_assignable: Maximum number of assignees (optional)
            prevent_guest_comment: Prevent guests from commenting (optional)

        Returns:
            Task object for the created task

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or invalid
        """
        self._validate_org_id(org_id)
        
        if not title or not isinstance(title, str):
            raise ValueError("Task title must be a non-empty string")

        if not deadline or not isinstance(deadline, str):
            raise ValueError("Task deadline must be a non-empty string in 'YYYY-mm-dd HH:ii:ss' format")

        # Validate that at least one assignee is provided
        if not owners or not isinstance(owners, TaskOwners):
            raise ValueError("TaskOwners object is required")
            
        if not owners.users and not owners.guests and not owners.groups:
            raise ValueError("At least one assignee is required (users, guests, or groups)")

        # Validate max_assignable if provided
        if max_assignable is not None and (not isinstance(max_assignable, int) or max_assignable <= 0):
            raise ValueError("max_assignable must be a positive integer")

        try:
            endpoint = f"organizations/{org_id}/tasks"

            task_data = {
                "title": title,
                "deadline": deadline,
                "owners": {
                    "users": owners.users or [],
                    "guests": owners.guests or [],
                    "groups": owners.groups or []
                },
                "task_type": "task",
                "separate_task_for_each_assignee": True,
                "status": "not-started",
                "everyone_must_complete": False,
                "is_soft_start_date": True
            }

            # Add optional fields
            if description:
                task_data["description"] = description
            if max_assignable is not None:
                task_data["max_assignable"] = max_assignable
            if prevent_guest_comment is not None:
                task_data["prevent_guest_comment"] = prevent_guest_comment

            response_data = self.sdk._make_request('POST', endpoint, data=task_data)

            task_response_data = self._extract_data(response_data)
            if task_response_data:
                # Handle both single task and list responses
                if isinstance(task_response_data, list) and task_response_data:
                    return Task.from_dict(task_response_data[0])
                elif isinstance(task_response_data, dict):
                    return Task.from_dict(task_response_data)
            
            # Check if response_data itself is the task data
            if isinstance(response_data, dict) and 'data' in response_data:
                task_data_response = response_data['data']
                if isinstance(task_data_response, dict):
                    return Task.from_dict(task_data_response)
            
            self.sdk.logger.warning("Unexpected response format for task creation")
            return None

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "create task", org_id=org_id, title=title)

    def create_simple_task(self, org_id: str, title: str, deadline: str, user_ids: Optional[list] = None, 
                          guest_emails: Optional[list] = None, group_ids: Optional[list] = None,
                          description: Optional[str] = None) -> Optional[Task]:
        """
        Create a simple task with basic assignee information.
        
        This is a convenience method that creates TaskOwners internally.

        Args:
            org_id: Organization ID
            title: Task name (required)
            deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
            user_ids: List of user IDs to assign (optional)
            guest_emails: List of guest email addresses to assign (optional)
            group_ids: List of group IDs to assign (optional)
            description: Task description (optional)

        Returns:
            Task object for the created task

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or no assignees provided
        """
        # Validate that at least one assignee type is provided
        if not user_ids and not guest_emails and not group_ids:
            raise ValueError("At least one assignee is required (user_ids, guest_emails, or group_ids)")

        # Create TaskOwners object
        owners = TaskOwners(
            users=user_ids or [],
            guests=guest_emails or [],
            groups=group_ids or []
        )

        return self.create_task(org_id, title, deadline, owners, description)

    def create_user_task(self, org_id: str, title: str, deadline: str, user_ids: list, 
                        description: Optional[str] = None) -> Optional[Task]:
        """
        Create a task assigned to specific users only.

        Args:
            org_id: Organization ID
            title: Task name (required)
            deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
            user_ids: List of user IDs to assign (required)
            description: Task description (optional)

        Returns:
            Task object for the created task

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing
        """
        if not user_ids or not isinstance(user_ids, list):
            raise ValueError("user_ids must be a non-empty list")

        return self.create_simple_task(org_id, title, deadline, user_ids=user_ids, description=description)

    def create_guest_task(self, org_id: str, title: str, deadline: str, guest_emails: list, 
                         description: Optional[str] = None) -> Optional[Task]:
        """
        Create a task assigned to guests only.

        Args:
            org_id: Organization ID
            title: Task name (required)
            deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
            guest_emails: List of guest email addresses to assign (required)
            description: Task description (optional)

        Returns:
            Task object for the created task

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing
        """
        if not guest_emails or not isinstance(guest_emails, list):
            raise ValueError("guest_emails must be a non-empty list")

        # Basic email validation
        for email in guest_emails:
            try:
                validation = validate_email(email)
                # The validated email address
                email = validation.normalized
            except EmailNotValidError as e:
                raise ValueError(f"Invalid email address: {str(e)}")

        return self.create_simple_task(org_id, title, deadline, guest_emails=guest_emails, description=description)

    def create_group_task(self, org_id: str, title: str, deadline: str, group_ids: list, 
                         description: Optional[str] = None) -> Optional[Task]:
        """
        Create a task assigned to groups only.

        Args:
            org_id: Organization ID
            title: Task name (required)
            deadline: Task deadline in "YYYY-mm-dd HH:ii:ss" format
            group_ids: List of group IDs to assign (required)
            description: Task description (optional)

        Returns:
            Task object for the created task

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing
        """
        if not group_ids or not isinstance(group_ids, list):
            raise ValueError("group_ids must be a non-empty list")

        return self.create_simple_task(org_id, title, deadline, group_ids=group_ids, description=description)

    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[str, Any]] = 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,
    ) -> Run:
        """
        Launch a new process (run) from a template.

        Args:
            org_id: Organization ID
            template_id: Template (checklist) ID to launch from
            name: Name for the new process run
            summary: Optional process summary
            owner_id: Optional user ID to set as process owner
            is_public: Whether the process is publicly accessible
            assign_someone_else: Allow assignees to reassign tasks
            publicly_hidden_fields: List of fields to hide publicly (e.g. ["comments"])
            prerun: List of kickoff (prerun) field value dicts, e.g.
                    [{"<field_id>": "value"}, ...]
            tasks: Per-step overrides keyed by step ID, e.g.
                   {"<step_id>": {"position": 1, "deadline": "2025-01-01 00:00:00", "max_assignable": 1}}
            tags: List of tag IDs to attach
            users: List of user IDs to assign to the process
            groups: List of group IDs to assign to the process
            roles: List of role assignment dicts with keys "users", "guests", "groups"
            folders: List of folder IDs to place the process in
            parent_id: Optional parent process ID

        Returns:
            Run object for the launched process

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or invalid
        """
        self._validate_org_id(org_id)

        if not template_id or not isinstance(template_id, str):
            raise ValueError("template_id must be a non-empty string")

        if not name or not isinstance(name, str):
            raise ValueError("name must be a non-empty string")

        try:
            endpoint = f"organizations/{org_id}/runs"

            body: Dict[str, Any] = {
                "name": name,
                "checklist_id": template_id,
            }

            if summary is not None:
                body["summary"] = summary
            if owner_id is not None:
                body["owner_id"] = owner_id
            if is_public is not None:
                body["is_public"] = is_public
            if assign_someone_else is not None:
                body["assign_someone_else"] = assign_someone_else
            if publicly_hidden_fields is not None:
                body["publicly_hidden_fields"] = publicly_hidden_fields
            if prerun is not None:
                body["prerun"] = prerun
            if tasks is not None:
                body["tasks"] = tasks
            if tags is not None:
                body["tags"] = tags
            if users is not None:
                body["users"] = users
            if groups is not None:
                body["groups"] = groups
            if roles is not None:
                body["roles"] = roles
            if folders is not None:
                body["folders"] = folders
            if parent_id is not None:
                body["parent_id"] = parent_id

            response_data = self.sdk._make_request('POST', endpoint, data=body)

            # API wraps the run in a 'data' key
            if isinstance(response_data, dict) and 'data' in response_data:
                run_data = response_data['data']
                if isinstance(run_data, dict):
                    return Run.from_dict(run_data)

            # Fallback: treat the response itself as the run object
            if isinstance(response_data, dict):
                return Run.from_dict(response_data)

            raise TallyfyError("Unexpected response format for launch_process")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "launch process", org_id=org_id, template_id=template_id, name=name)

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

        Args:
            org_id: Organization ID
            run_id: Run (process) ID
            with_: Comma-separated includes. Valid values:
                checklist, tasks, tags, assets, next_task, permissions,
                tasks_meta, ko_form_fields, form_fields, stages, folders,
                selected_text_comment, member_watchers, guest_watchers

        Returns:
            Run object

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        try:
            params = self._build_query_params(with_=with_)
            response_data = self.sdk._make_request('GET', f"organizations/{org_id}/runs/{run_id}", params=params)

            if isinstance(response_data, dict) and 'data' in response_data:
                run_data = response_data['data']
                if isinstance(run_data, dict):
                    return Run.from_dict(run_data)

            if isinstance(response_data, dict):
                return Run.from_dict(response_data)

            raise TallyfyError("Unexpected response format for get_process")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "get process", org_id=org_id, run_id=run_id)

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

        Args:
            org_id: Organization ID
            run_id: Run (process) ID
            name: New process name
            summary: New process summary
            starred: Whether to star/unstar the process

        Returns:
            Updated Run object

        Raises:
            TallyfyError: If the request fails
            ValueError: If no fields are provided or required params are missing
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        if name is None and summary is None and starred is None:
            raise ValueError("At least one of name, summary, or starred must be provided")

        try:
            body: Dict[str, Any] = {}
            if name is not None:
                body["name"] = name
            if summary is not None:
                body["summary"] = summary
            if starred is not None:
                body["starred"] = starred

            response_data = self.sdk._make_request('PUT', f"organizations/{org_id}/runs/{run_id}", data=body)

            if isinstance(response_data, dict) and 'data' in response_data:
                run_data = response_data['data']
                if isinstance(run_data, dict):
                    return Run.from_dict(run_data)

            if isinstance(response_data, dict):
                return Run.from_dict(response_data)

            raise TallyfyError("Unexpected response format for update_process")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "update process", org_id=org_id, run_id=run_id)

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

        Archived processes can be retrieved with get_organization_runs(archived='only').

        Args:
            org_id: Organization ID
            run_id: Run (process) ID to archive

        Returns:
            True if archived successfully

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        try:
            self.sdk._make_request('DELETE', f"organizations/{org_id}/runs/{run_id}")
            return True

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "archive process", org_id=org_id, run_id=run_id)

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

        Args:
            org_id: Organization ID
            run_id: Run (process) ID
            task_id: Task ID to complete
            is_approved: Optional approval flag
            override_user: Optional user ID to record as the completing user

        Returns:
            Updated Task object

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or invalid
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        if not task_id or not isinstance(task_id, str):
            raise ValueError("task_id must be a non-empty string")

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/completed-tasks"

            body: Dict[str, Any] = {"task_id": task_id}
            if is_approved is not None:
                body["is_approved"] = is_approved
            if override_user is not None:
                body["override_user"] = override_user

            response_data = self.sdk._make_request('POST', endpoint, data=body)

            if isinstance(response_data, dict) and 'data' in response_data:
                task_data = response_data['data']
                if isinstance(task_data, dict):
                    return Task.from_dict(task_data)

            if isinstance(response_data, dict):
                return Task.from_dict(response_data)

            raise TallyfyError("Unexpected response format for complete_task")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "complete task", org_id=org_id, run_id=run_id, task_id=task_id)

    def reopen_task(self, org_id: str, run_id: str, task_id: str) -> Task:
        """
        Reopen a previously completed task.

        Args:
            org_id: Organization ID
            run_id: Run (process) ID
            task_id: Task ID to reopen

        Returns:
            Updated Task object

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or invalid
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        if not task_id or not isinstance(task_id, str):
            raise ValueError("task_id must be a non-empty string")

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/completed-tasks/{task_id}"

            response_data = self.sdk._make_request('DELETE', endpoint)

            if isinstance(response_data, dict) and 'data' in response_data:
                task_data = response_data['data']
                if isinstance(task_data, dict):
                    return Task.from_dict(task_data)

            if isinstance(response_data, dict):
                return Task.from_dict(response_data)

            raise TallyfyError("Unexpected response format for reopen_task")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "reopen task", org_id=org_id, run_id=run_id, task_id=task_id)

    def update_task(self, org_id: str, run_id: str, task_id: str,
                    taskdata: Optional[Dict[str, Any]] = None,
                    owners: Optional[Dict[str, Any]] = None,
                    status: Optional[str] = None,
                    title: Optional[str] = None,
                    summary: Optional[str] = None,
                    position: Optional[int] = None,
                    max_assignable: Optional[int] = None,
                    started_at: Optional[str] = None,
                    deadline: Optional[str] = None,
                    task_type: Optional[str] = None,
                    top_secret: Optional[bool] = None,
                    webhook: Optional[str] = None,
                    prevent_guest_comment: Optional[bool] = None) -> Task:
        """
        Update properties of a task within a process run.

        Args:
            org_id: Organization ID
            run_id: Run (process) ID
            task_id: Task ID to update
            taskdata: Form field values for the task
            owners: Assignees dict with keys "users", "guests", "groups"
            status: Task status
            title: Task title
            summary: Task description/summary
            position: Task position (1-based)
            max_assignable: Maximum number of assignees
            started_at: Task start date/time
            deadline: Task deadline (e.g. "2099-12-31 23:59:59")
            task_type: Task type
            top_secret: Hide task from non-assignees
            webhook: Webhook URL called on task completion
            prevent_guest_comment: Prevent guests from commenting

        Returns:
            Updated Task object

        Raises:
            TallyfyError: If the request fails
            ValueError: If required parameters are missing or invalid
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        if not task_id or not isinstance(task_id, str):
            raise ValueError("task_id must be a non-empty string")

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/tasks/{task_id}"

            body: Dict[str, Any] = {}
            if taskdata is not None:
                body["taskdata"] = taskdata
            if owners is not None:
                body["owners"] = owners
            if status is not None:
                body["status"] = status
            if title is not None:
                body["title"] = title
            if summary is not None:
                body["summary"] = summary
            if position is not None:
                body["position"] = position
            if max_assignable is not None:
                body["max_assignable"] = max_assignable
            if started_at is not None:
                body["started_at"] = started_at
            if deadline is not None:
                body["deadline"] = deadline
            if task_type is not None:
                body["task_type"] = task_type
            if top_secret is not None:
                body["top_secret"] = top_secret
            if webhook is not None:
                body["webhook"] = webhook
            if prevent_guest_comment is not None:
                body["prevent_guest_comment"] = prevent_guest_comment

            response_data = self.sdk._make_request('PUT', endpoint, data=body)

            if isinstance(response_data, dict) and 'data' in response_data:
                task_data = response_data['data']
                if isinstance(task_data, dict):
                    return Task.from_dict(task_data)

            if isinstance(response_data, dict):
                return Task.from_dict(response_data)

            raise TallyfyError("Unexpected response format for update_task")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "update task", org_id=org_id, run_id=run_id, task_id=task_id)

    def get_standalone_task(self, org_id: str, task_id: str) -> Task:
        """
        Get a standalone (one-off) task by ID.

        Args:
            org_id: Organization ID
            task_id: Task ID

        Returns:
            Task object

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)

        try:
            endpoint = f"organizations/{org_id}/tasks/{task_id}"
            response_data = self.sdk._make_request('GET', endpoint)

            if isinstance(response_data, dict) and 'data' in response_data:
                task_data = response_data['data']
                if isinstance(task_data, dict):
                    return Task.from_dict(task_data)

            if isinstance(response_data, dict):
                return Task.from_dict(response_data)

            raise TallyfyError("Unexpected response format for get_standalone_task")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "get standalone task", org_id=org_id, task_id=task_id)

    def update_standalone_task(self, org_id: str, task_id: str,
                               title: Optional[str] = None,
                               summary: Optional[str] = None,
                               deadline: Optional[str] = None,
                               owners: Optional[Dict[str, Any]] = None,
                               status: Optional[str] = None,
                               max_assignable: Optional[int] = None,
                               prevent_guest_comment: Optional[bool] = None) -> Task:
        """
        Update a standalone (one-off) task.

        Args:
            org_id: Organization ID
            task_id: Task ID
            title: New task title
            summary: New task description
            deadline: New deadline in 'YYYY-mm-dd HH:ii:ss' format
            owners: Assignees dict with keys 'users', 'guests', 'groups'
            status: Task status
            max_assignable: Maximum number of assignees
            prevent_guest_comment: Prevent guests from commenting

        Returns:
            Updated Task object

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)

        try:
            endpoint = f"organizations/{org_id}/tasks/{task_id}"

            body: Dict[str, Any] = {}
            if title is not None:
                body['title'] = title
            if summary is not None:
                body['summary'] = summary
            if deadline is not None:
                body['deadline'] = deadline
            if owners is not None:
                body['owners'] = owners
            if status is not None:
                body['status'] = status
            if max_assignable is not None:
                body['max_assignable'] = max_assignable
            if prevent_guest_comment is not None:
                body['prevent_guest_comment'] = prevent_guest_comment

            response_data = self.sdk._make_request('PUT', endpoint, data=body)

            if isinstance(response_data, dict) and 'data' in response_data:
                task_data = response_data['data']
                if isinstance(task_data, dict):
                    return Task.from_dict(task_data)

            if isinstance(response_data, dict):
                return Task.from_dict(response_data)

            raise TallyfyError("Unexpected response format for update_standalone_task")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "update standalone task", org_id=org_id, task_id=task_id)

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

        Args:
            org_id: Organization ID
            task_id: Task ID

        Returns:
            True if deleted successfully

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)

        try:
            endpoint = f"organizations/{org_id}/tasks/{task_id}"
            self.sdk._make_request('DELETE', endpoint)
            return True

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "delete standalone task", org_id=org_id, task_id=task_id)

    def reactivate_process(self, org_id: str, run_id: str) -> Run:
        """
        Reactivate an archived process.

        Args:
            org_id: Organization ID
            run_id: Run (process) ID to reactivate

        Returns:
            Reactivated Run object

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/activate"
            response_data = self.sdk._make_request('PUT', endpoint)

            if isinstance(response_data, dict) and 'data' in response_data:
                run_data = response_data['data']
                if isinstance(run_data, dict):
                    return Run.from_dict(run_data)

            if isinstance(response_data, dict):
                return Run.from_dict(response_data)

            raise TallyfyError("Unexpected response format for reactivate_process")

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "reactivate process", org_id=org_id, run_id=run_id)

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

        Args:
            org_id: Organization ID
            run_id: Run (process) ID

        Returns:
            True if completed successfully

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/complete-prerun"
            self.sdk._make_request('POST', endpoint)
            return True

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "complete kickoff form", org_id=org_id, run_id=run_id)

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

        Args:
            org_id: Organization ID
            run_id: Run (process) ID

        Returns:
            True if reopened successfully

        Raises:
            TallyfyError: If the request fails
        """
        self._validate_org_id(org_id)
        self._validate_process_id(run_id)

        try:
            endpoint = f"organizations/{org_id}/runs/{run_id}/complete-prerun"
            self.sdk._make_request('DELETE', endpoint)
            return True

        except TallyfyError:
            raise
        except Exception as e:
            self._handle_api_error(e, "reopen kickoff form", org_id=org_id, run_id=run_id)