#!/usr/bin/env python3

"""
installation-birthday
Receive congratulations on system installation anniversary.

Copyright © 2017 Chris Lamb <lamby@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import os
import sys
import logging
import argparse
import datetime
import platform
import subprocess

DESCRIPTION = "Receive congratulations on system installation anniversaries."

TEMPLATE = """
                  0   0
                  |   |
              ____|___|____
           0  |~ ~ ~ ~ ~ ~|   0
           |  |           |   |
        ___|__|___________|___|__
        |/\/\/\/\/\/\/\/\/\/\/\/|
    0   |       H a p p y       |   0
    |   |/\/\/\/\/\/\/\/\/\/\/\/|   |
   _|___|_______________________|___|__
  |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/|
  |                                   |
  |         B i r t h d a y! ! !      |
  | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |
  |___________________________________|


Congratulations, your {distname} system "{node}" was installed
{age} year(s) ago today!


Best wishes,

Your local system administrator
"""


class Birthday(object):
    def main(self):
        self.args = self.parse_arguments()
        self.setup_logging()

        now = datetime.datetime.utcnow()
        install = self.get_installation_datetime()

        if install is None:
            self.log.debug("Could not determine installation date")
            return 1

        if self.args.force:
            install = now.replace(year=now.year - 2)

        self.log.debug("Today's date: %s", now.date())
        self.log.info("Installation date: %s", install.date())

        age = now.year - install.year

        if age == 0:
            self.log.debug("Not reporting for today's year")
            return 0

        if (install.month, install.day) != (now.month, now.day):
            self.log.debug("Dates do not match")
            return 0

        print(TEMPLATE.format(
            age=age,
            node=platform.node(),
            distname=platform.linux_distribution()[0].title(),
        ))

        return 0

    def parse_arguments(self):
        parser = argparse.ArgumentParser(description=DESCRIPTION)

        parser.add_argument(
            '--verbosity',
            dest='verbosity',
            type=int,
            default=1,
            choices=(0, 1, 2),
            help="set log level",
        )

        parser.add_argument(
            '--force',
            dest='force',
            action='store_true',
            default=False,
            help="celebrate regardless of today's date",
        )

        return parser.parse_args()

    def setup_logging(self):
        self.log = logging.getLogger()

        self.log.setLevel({
            0: logging.WARNING,
            1: logging.INFO,
            2: logging.DEBUG,
        }[self.args.verbosity])

        handler = logging.StreamHandler(sys.stderr)
        handler.setFormatter(
            logging.Formatter('%(levelname).1s: %(message)s')
        )
        self.log.addHandler(handler)

    def check_output(self, *args, **kwargs):
        kwargs.setdefault('stderr', subprocess.DEVNULL)

        output = subprocess.check_output(*args, **kwargs).decode('utf-8')

        return output.splitlines()

    def get_block_device(self, path):
        self.log.debug("Determining block device for %s", path)

        lines = self.check_output((
            'df',
            '--portability',
            '--local',
            '--type=ext2', '--type=ext3', '--type=ext4',
            path,
        ))

        return lines[1].split(' ')[0]

    def get_installation_datetime(self):
        try:
            device = self.get_block_device('/')

            self.log.debug("Determining creation time for %s", device)
            lines = self.check_output((
                '/sbin/tune2fs',
                '-l',
                device,
            ))
        except (OSError, subprocess.CalledProcessError):
            pass
        else:
            lookup = {}
            for x in lines:
                if ':' in x:
                    k, v = x.split(':', 1)
                    lookup[k] = v.strip()

            try:
                dt = datetime.datetime.strptime(
                    lookup['Filesystem created'].strip(),
                    '%a %b %d %H:%M:%S %Y',
                )

                self.log.debug("Using creation time from %s", device)

                return dt
            except (KeyError, ValueError):
                pass

        for x in (
            '/var/log/installer',
            '/var/log/bootstrap.log',
            '/lost+found',
            '/root',
            '/etc/machine-id',
        ):
            self.log.debug("Checking mtime of %s", x)

            try:
                dt = datetime.datetime.utcfromtimestamp(os.stat(x).st_mtime)
            except Exception:
                self.log.debug("Failed to get mtime of %s", x)
                continue

            self.log.debug("Using mtime from %s", x)

            return dt

        # Could not determine a time.
        return None


if __name__ == '__main__':
    sys.exit(Birthday().main())
