# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""Input validation for GitHub webhook payload fields.

Validates fields from webhook payloads before they are used as DynamoDB keys,
S3 paths, or other trusted identifiers. All validators raise ValidationError
on invalid input.
"""

from __future__ import annotations

import re


class ValidationError(ValueError):
    """Raised when a webhook payload field fails validation."""


def validate_org_id(value: str) -> str:
    """Validate a GitHub organization/owner identifier.

    Args:
        value: Organization or user login from webhook payload.

    Returns:
        The validated string.

    Raises:
        ValidationError: If the value is empty, too long, contains invalid
            characters, or includes path traversal sequences.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("org_id must be a non-empty string")
    if len(value) > 255:
        raise ValidationError(f"org_id too long: {len(value)} > 255")
    if ".." in value:
        raise ValidationError("org_id must not contain '..'")
    if not re.fullmatch(r"[a-zA-Z0-9._-]+", value):
        raise ValidationError(f"org_id contains invalid characters: {value!r}")
    return value


def validate_repo(value: str) -> str:
    """Validate a repository name (may include owner prefix like owner/repo).

    Args:
        value: Repository full name from webhook payload.

    Returns:
        The validated string.

    Raises:
        ValidationError: If invalid.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("repo must be a non-empty string")
    if len(value) > 512:
        raise ValidationError(f"repo too long: {len(value)} > 512")
    if ".." in value:
        raise ValidationError("repo must not contain '..'")
    if not re.fullmatch(r"[a-zA-Z0-9._/-]+", value):
        raise ValidationError(f"repo contains invalid characters: {value!r}")
    return value


def validate_commit_sha(value: str) -> str:
    """Validate a git commit SHA (short or full).

    Args:
        value: Commit SHA from webhook payload.

    Returns:
        The validated string.

    Raises:
        ValidationError: If not a valid hex string of 7-40 characters.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("commit_sha must be a non-empty string")
    if not re.fullmatch(r"[0-9a-f]{7,40}", value):
        raise ValidationError(f"commit_sha must be 7-40 hex chars: {value!r}")
    return value


def validate_branch(value: str) -> str:
    """Validate a git branch name.

    Args:
        value: Branch ref from webhook payload.

    Returns:
        The validated string.

    Raises:
        ValidationError: If invalid.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("branch must be a non-empty string")
    if len(value) > 255:
        raise ValidationError(f"branch too long: {len(value)} > 255")
    if not re.fullmatch(r"[a-zA-Z0-9._/-]+", value):
        raise ValidationError(f"branch contains invalid characters: {value!r}")
    return value


def validate_author(value: str) -> str:
    """Validate a GitHub username.

    Args:
        value: Author login from webhook payload.

    Returns:
        The validated string.

    Raises:
        ValidationError: If invalid.
    """
    if not value or not isinstance(value, str):
        raise ValidationError("author must be a non-empty string")
    if len(value) > 255:
        raise ValidationError(f"author too long: {len(value)} > 255")
    if not re.fullmatch(r"[a-zA-Z0-9._-]+", value):
        raise ValidationError(f"author contains invalid characters: {value!r}")
    return value


def validate_pr_number(value: int) -> int:
    """Validate a pull request number.

    Args:
        value: PR number from webhook payload.

    Returns:
        The validated integer.

    Raises:
        ValidationError: If not a positive integer or exceeds max.
    """
    if not isinstance(value, int) or isinstance(value, bool):
        raise ValidationError(f"pr_number must be an integer, got {type(value).__name__}")
    if value < 1 or value > 999999:
        raise ValidationError(f"pr_number out of range: {value}")
    return value
