Task: gitlab.update-issue
Goal: Assign the open issue regarding linking to an accessibility statement in byteblaze/a11y-webring.club to Rohan.
Task configuration:
account: Rohan
issue: linking to an accessibility statement
namespace: byteblaze
project_name: a11y-webring.club

Validation code:
from typing import Any, Dict, List
from urllib.parse import quote

import requests
import json
import os

def _get_headers():
    return {
        "Content-Type": "application/json",
        "Accept": "application/json",
        **json.loads(os.getenv("GITLAB_EXTRA_HTTP_HEADERS", "{}")),
    }

def _get_current_user():
    url = os.getenv("GITLAB_BASE_URL", "").rstrip("/") + "/api/v4/user"
    response = requests.get(url=url, headers=_get_headers())
    if response.status_code == 200:
        return response.json()
    return None

def _search_issues_globally(search_query):
    """Search for issues across all projects."""
    url = (
        os.getenv("GITLAB_BASE_URL", "").rstrip("/")
        + "/api/v4/issues"
        + "?search=" + quote(search_query)
        + "&in=title"
        + "&per_page=100"
    )
    response = requests.get(url=url, headers=_get_headers())
    if response.status_code != 200:
        return []
    return response.json()

def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    # Check whether we have the necessary information to validate
    if "issue" not in config or "account" not in config:
        raise ValueError("Target must contain 'issue' and 'account'")

    # check whether we have messages
    if not messages:
        raise ValueError("Messages cannot be empty")

    # Get project_name from either 'project_name' or 'repo' field
    project_name = config.get("project_name") or config.get("repo")

    if "namespace" in config and project_name:
        # Search within the specific project
        project_id = quote(f"{config['namespace']}/{project_name}", safe="")
        url = os.getenv("GITLAB_BASE_URL", "").rstrip("/") + "/api/v4/projects/" + project_id + "/issues"

        all_issues = []
        per_page = 100
        page = 1
        while True:
            response = requests.get(
                url=url,
                headers=_get_headers(),
                params={"per_page": per_page, "page": page},
            )
            if response.status_code != 200:
                return 0
            result = response.json()
            all_issues.extend(result)
            if len(result) < per_page:
                break
            page += 1
    else:
        # No namespace/project_name - search globally
        all_issues = _search_issues_globally(config["issue"])

    # Find matching issues (flexible title matching), prefer open issues
    matching_issues = [
        issue for issue in all_issues
        if config["issue"].lower() in issue["title"].lower()
    ]
    if not matching_issues:
        return 0

    # Prefer open issues over closed ones
    open_matches = [i for i in matching_issues if i["state"] == "opened"]
    issue_info = open_matches[0] if open_matches else matching_issues[0]

    # Parse account(s) - handle "myself and primer", "myself", etc.
    account_str = config["account"]
    accounts = [a.strip() for a in account_str.replace(" and ", ", ").split(",")]

    current_user = None
    # Resolve "myself" to current user
    resolved_accounts = []
    for acc in accounts:
        if acc.lower() == "myself":
            if current_user is None:
                current_user = _get_current_user()
                if current_user is None:
                    return 0
            resolved_accounts.append(
                (current_user["username"].lower(), current_user["name"].lower())
            )
        else:
            resolved_accounts.append((acc.lower(), acc.lower()))

    # Check if the issue is assigned to all specified accounts
    if not issue_info["assignees"]:
        return 0

    for username_or_name, display_name in resolved_accounts:
        found = False
        for assignee in issue_info["assignees"]:
            if (username_or_name in assignee["name"].lower()
                or username_or_name in assignee["username"].lower()
                or display_name in assignee["name"].lower()
                or display_name in assignee["username"].lower()):
                found = True
                break
        if not found:
            return 0

    return 1