#!/usr/bin/env python3
#
# Copyright (c) 2025 The NetBSD Foundation, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

"""validdate is a hook to verify incoming changesets have reasonable dates.

Usage:
  [hooks]
  pretxnchangegroup.validdate =
     python:....validdate.hook
"""

import datetime
import os
import time

from typing import Optional

from mercurial.i18n import _
from mercurial.utils import dateutil
from mercurial import (
    error,
    localrepo,
    pycompat,
    ui,
)


def hook(
    ui: ui.ui,
    repo: localrepo.localrepository,
    hooktype: bytes,
    node: Optional[bytes] = None,
    **kwargs,
) -> None:
    if hooktype != b'pretxnchangegroup':
        raise error.Abort(
            _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype)
        )

    if node is None:
        # No new changesets, nothing to reject.
        return
    nowstr = os.getenv('DATE', None)
    if nowstr:
        now = datetime.datetime.fromisoformat(nowstr).timestamp()
    else:
        now = time.time()
    ctx = repo.unfiltered()[node]
    bad = {}
    for revno in repo.changelog.revs(start=ctx.rev()):
        rev = repo.unfiltered()[revno]
        seconds_since_epoch, _tzoffset_seconds = rev.date()
        if seconds_since_epoch < 0:
            bad[revno] = _(b'predates 1970 Unix epoch')
        elif seconds_since_epoch >= now:
            bad[revno] = _(b'from the future')
    if bad:
        for revno, reason in bad.items():
            rev = repo.unfiltered()[revno]
            ui.error(_(b'changeset %s (%s): %s\n') % (
                rev,
                dateutil.datestr(rev.date()),
                reason,
            ))
        raise error.Abort(_(b'rejecting changesets with bogus dates'))
