"""Event router — P3.1b + V2.2c

Parses GitHub webhook payloads, classifies event type, extracts metadata.
Returns a structured WebhookEvent / InstallationEvent, or None if non-actionable.
"""
from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Optional, Union

logger = logging.getLogger(__name__)

# File patterns that indicate security-relevant changes
_SECURITY_SENSITIVE_PATTERNS = [
    "requirements", "pyproject.toml", "setup.cfg", "setup.py",
    "Pipfile", "Pipfile.lock", "poetry.lock",
    ".env", "config", "auth", "security", "middleware",
    "settings", "secrets", "credentials",
]


@dataclass
class WebhookEvent:
    event_type: str                         # "push" | "pull_request"
    commit_sha: str
    repo_full_name: str                     # "owner/repo"
    clone_url: str
    branch: str
    changed_files: list[str] = field(default_factory=list)
    pr_number: Optional[int] = None
    is_security_relevant: bool = False
    action: Optional[str] = None           # for PR: "opened", "synchronize", etc.
    installation_id: Optional[int] = None  # GitHub App installation ID (V2.2c)


@dataclass
class InstallationEvent:
    """GitHub App installation / installation_repositories event."""
    event_type: str                         # "installation" | "installation_repositories"
    action: str                             # "created" | "deleted" | "added" | "removed"
    installation_id: int
    account_login: str                      # org or user that installed the App
    repos: list[str] = field(default_factory=list)  # "owner/repo" strings affected


def route_event(event_type: str, payload: dict) -> Optional[Union[WebhookEvent, InstallationEvent]]:
    """
    Parse a GitHub webhook payload and return a WebhookEvent or InstallationEvent,
    or None if non-actionable.

    Supported events:
    - push
    - pull_request (opened, synchronize, reopened)
    - installation (created, deleted)
    - installation_repositories (added, removed)
    - ping
    """
    if event_type == "push":
        return _handle_push(payload)
    elif event_type == "pull_request":
        return _handle_pull_request(payload)
    elif event_type in ("installation", "installation_repositories"):
        return _handle_installation(event_type, payload)
    elif event_type == "ping":
        logger.info("Received ping from GitHub — webhook configured correctly")
        return None
    else:
        logger.debug("Ignoring unsupported event type: %s", event_type)
        return None


def _handle_push(payload: dict) -> Optional[WebhookEvent]:
    """Parse a push event payload."""
    repo = payload.get("repository", {})
    commits = payload.get("commits", [])

    if not commits:
        return None  # empty push (e.g., branch creation without commits)

    # Use the HEAD commit SHA
    head_commit = payload.get("head_commit") or commits[-1]
    commit_sha = payload.get("after") or head_commit.get("id", "")

    if not commit_sha or commit_sha == "0" * 40:
        return None  # branch deletion

    # Collect changed files across all commits
    changed_files: set[str] = set()
    for commit in commits:
        changed_files.update(commit.get("added", []))
        changed_files.update(commit.get("modified", []))
        changed_files.update(commit.get("removed", []))

    branch = payload.get("ref", "").replace("refs/heads/", "")

    installation = payload.get("installation") or {}
    installation_id = installation.get("id") if installation else None

    event = WebhookEvent(
        event_type="push",
        commit_sha=commit_sha,
        repo_full_name=repo.get("full_name", ""),
        clone_url=repo.get("clone_url", ""),
        branch=branch,
        changed_files=sorted(changed_files),
        is_security_relevant=_is_security_relevant(list(changed_files)),
        installation_id=installation_id,
    )
    logger.info(
        "Push event: %s@%s — %d files changed",
        event.repo_full_name, commit_sha[:8], len(changed_files),
    )
    return event


def _handle_pull_request(payload: dict) -> Optional[WebhookEvent]:
    """Parse a pull_request event payload."""
    action = payload.get("action", "")
    if action not in ("opened", "synchronize", "reopened"):
        return None  # ignore close, label, etc.

    pr = payload.get("pull_request", {})
    repo = payload.get("repository", {})

    commit_sha = pr.get("head", {}).get("sha", "")
    pr_number = payload.get("number")
    branch = pr.get("head", {}).get("ref", "")

    installation = payload.get("installation") or {}
    installation_id = installation.get("id") if installation else None

    # For PRs, we don't have the file list in the webhook payload —
    # we'll compute it from git diff in the scope analyzer
    event = WebhookEvent(
        event_type="pull_request",
        commit_sha=commit_sha,
        repo_full_name=repo.get("full_name", ""),
        clone_url=repo.get("clone_url", ""),
        branch=branch,
        changed_files=[],  # populated by scope analyzer from git diff
        pr_number=pr_number,
        is_security_relevant=True,  # always analyze PRs
        action=action,
        installation_id=installation_id,
    )
    logger.info(
        "PR event: %s #%s (%s) — SHA %s",
        event.repo_full_name, pr_number, action, commit_sha[:8],
    )
    return event


def _handle_installation(event_type: str, payload: dict) -> Optional[InstallationEvent]:
    """Parse installation and installation_repositories events."""
    action = payload.get("action", "")
    installation = payload.get("installation", {})
    installation_id = installation.get("id")
    if not installation_id:
        logger.warning("Installation event missing installation.id")
        return None

    account = installation.get("account", {})
    account_login = account.get("login", "")

    repos: list[str] = []
    if event_type == "installation":
        # "created" includes a repositories array
        for repo in payload.get("repositories", []):
            full_name = repo.get("full_name", "")
            if full_name:
                repos.append(full_name)
    else:
        # installation_repositories: repos_added / repos_removed
        key = "repositories_added" if action == "added" else "repositories_removed"
        for repo in payload.get(key, []):
            full_name = repo.get("full_name", "")
            if full_name:
                repos.append(full_name)

    event = InstallationEvent(
        event_type=event_type,
        action=action,
        installation_id=installation_id,
        account_login=account_login,
        repos=repos,
    )
    logger.info(
        "Installation event: %s/%s — installation %s — %d repos",
        event_type, action, installation_id, len(repos),
    )
    return event


def _is_security_relevant(changed_files: list[str]) -> bool:
    """Heuristic: are any changed files security-sensitive?"""
    for f in changed_files:
        f_lower = f.lower()
        if f_lower.endswith(".py"):
            return True  # all Python changes are relevant
        if any(pattern in f_lower for pattern in _SECURITY_SENSITIVE_PATTERNS):
            return True
    return False
