"""
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 <= 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