"""
Server-side hooks that notify a running pygitweb instance about ref changes so its
long-polling `updates=true` subscribers can wake up immediately.
"""

from __future__ import annotations

import urllib.error
import urllib.parse
import urllib.request
from sys import stderr

import pygit2

from pygittools.hooks import HookResult, PostCommit, PostReceive, PreReceive, RefUpdate

DEFAULT_NOTIFY_TIMEOUT_SECONDS: float = 5.0


def _post_notify(notify_url: str, project: str, timeout: float) -> bool:
    url: str = f"{notify_url.rstrip('/')}?{urllib.parse.urlencode({'project': project})}"
    req: urllib.request.Request = urllib.request.Request(url, data=b"", method="POST")
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return 200 <= int(resp.status) < 300
    except (urllib.error.URLError, OSError, ValueError) as exc:
        stderr.write(f"notify {url!r} failed: {exc}\n")
        return False


class PreReceiveNotify(PreReceive):
    """Pre-receive hook variant that POSTs `<notify_url>?project=<project>` to wake subscribers.

    Notification failures never reject the push: they are reported on stderr only.
    Refs are not yet visible at pre-receive time, so subscribers may briefly see stale data.
    Prefer PostReceiveNotify for change-queue use cases.
    """

    def __init__(
        self,
        repo: pygit2.Repository,
        notify_url: str,
        project: str,
        timeout: float = DEFAULT_NOTIFY_TIMEOUT_SECONDS,
    ) -> None:
        super().__init__(repo)
        self.notify_url: str = notify_url
        self.project: str = project
        self.timeout: float = timeout

    def run(self, ref_updates: list[RefUpdate]) -> HookResult:
        if not ref_updates:
            return HookResult.SUCCESS
        _post_notify(self.notify_url, self.project, self.timeout)
        return HookResult.SUCCESS


class PostReceiveNotify(PostReceive):
    """Post-receive hook variant that POSTs `<notify_url>?project=<project>` after refs update."""

    def __init__(
        self,
        repo: pygit2.Repository,
        notify_url: str,
        project: str,
        timeout: float = DEFAULT_NOTIFY_TIMEOUT_SECONDS,
    ) -> None:
        super().__init__(repo)
        self.notify_url: str = notify_url
        self.project: str = project
        self.timeout: float = timeout

    def run(self, ref_updates: list[RefUpdate]) -> HookResult:
        if not ref_updates:
            return HookResult.SUCCESS
        _post_notify(self.notify_url, self.project, self.timeout)
        return HookResult.SUCCESS


class PostCommitNotify(PostCommit):
    """Post-commit hook variant for local clients: POSTs to a pygitweb instance after each commit.

    Pair with PostReceiveNotify on the server so long-poll subscribers wake on both push events
    and local commits made in working clones served by the same pygitweb instance.
    """

    def __init__(
        self,
        repo: pygit2.Repository,
        notify_url: str,
        project: str,
        timeout: float = DEFAULT_NOTIFY_TIMEOUT_SECONDS,
    ) -> None:
        super().__init__(repo)
        self.notify_url: str = notify_url
        self.project: str = project
        self.timeout: float = timeout

    def run(self) -> HookResult:
        _post_notify(self.notify_url, self.project, self.timeout)
        return HookResult.SUCCESS