#!/usr/bin/env python3

from json import load
from subprocess import check_call, check_output
from sys import argv
from time import time

NODE = argv[1]

NOW = int(time())
DAY_SECONDS = 60 * 60 * 24
INTERVALS = {
    'daily': DAY_SECONDS,
    'weekly': 7 * DAY_SECONDS,
    'monthly': 30 * DAY_SECONDS,
}

buckets = {}

def syslog(msg):
    check_output(['logger', '-t', f'backup-{NODE}', msg])


with open(f'/etc/backup-server/config.json', 'r') as f:
    server_settings = load(f)

with open(f'/etc/backup-server/clients/{NODE}', 'r') as f:
    client_settings = load(f)

# get all existing snapshots for NODE
for line in check_output('LC_ALL=C zfs list -H -t snapshot -o name', shell=True).splitlines():
    line = line.decode('UTF-8')

    if line.startswith('{}/{}@'.format(server_settings['zfs-base'], NODE)):
        _, snapname = line.split('@', 1)

        if 'zfs-auto-snap' in snapname:
            # migration from auto-snapshots, ignore
            continue

        ts, bucket = snapname.split('-', 1)
        buckets.setdefault(bucket, set()).add(int(ts))
        syslog(f'classified {line} as {bucket} from {ts}')

# determine if we need to create a new snapshot
for bucket in INTERVALS.keys():
    snapshots = sorted(buckets.get(bucket, set()))

    if snapshots:
        last_snap = snapshots[-1]
        delta = NOW - last_snap
        fresh_age = INTERVALS[bucket] - DAY_SECONDS

        if delta > fresh_age:
            # last snapshot is older than what we want. create a new one.
            check_call(
                'zfs snapshot {}/{}@{}-{}'.format(
                    server_settings['zfs-base'],
                    NODE,
                    NOW,
                    bucket,
                ),
                shell=True,
            )
            buckets.setdefault(bucket, set()).add(NOW)
            syslog(f'created new snapshot {NOW}-{bucket}')
        else:
            syslog(f'existing snapshot {last_snap}-{bucket} is fresh enough')
    else:
        check_call(
            'zfs snapshot {}/{}@{}-{}'.format(
                server_settings['zfs-base'],
                NODE,
                NOW,
                bucket,
            ),
            shell=True,
        )
        buckets.setdefault(bucket, set()).add(NOW)
        syslog(f'created initial snapshot {NOW}-{bucket}')

# finally, see if we can delete any snapshots, because they are old enough
for bucket in INTERVALS.keys():
    snapshots = sorted(buckets.get(bucket, set()))

    if not snapshots:
        syslog(f'something is wrong, there are no snapshots for {bucket}')
        continue

    # see comment in zfs-auto-snapshot about doing +1 here
    keep_age = INTERVALS[bucket] * (client_settings[bucket]+1)

    # oldest snapshots come first
    for ts in snapshots[:-int(client_settings[bucket])]:
        delta = NOW - ts

        if delta >= keep_age:
            check_call(
                'zfs destroy {}/{}@{}-{}'.format(
                    server_settings['zfs-base'],
                    NODE,
                    ts,
                    bucket,
                ),
                shell=True,
            )
            syslog(f'removing snapshot {ts}-{bucket}, age {delta}, keep_age {keep_age}')
        else:
            syslog(f'keeping snapshot {ts}-{bucket}, age not reached')

    for ts in snapshots[int(client_settings[bucket]):]:
        syslog(f'keeping snapshot {ts}-{bucket}, count')
