"""
Imunify integration script for ISPManager.
Collect users' certificates that were created/imported via ispmanager
and add it to ssl-cache

To execute script, just run
python3 ./cache_ssl_certs.py

Then check whether new certificates appeared or not:
im360-ssl-cache

The script is intended to run without args and doesn't output anything
to stdout. If an error was encountered, traceback will be printed

"""
import json
import re
import sys

from pathlib import Path
from subprocess import check_output, run, PIPE

ISPMGR_CERTS_ROOT = Path('/var/www/httpd-cert/')


def get_ispmgr_data(section):
    assert section in ('sslcert',)
    ispmgr_output = check_output([
        '/usr/local/mgr5/sbin/mgrctl',
        '-m',
        'ispmgr',
        section,
    ]).decode().strip().split('\n')
    return [
        # parse string like 'k1=v1 k2=v2 v3 v4 k3=v5'
        dict(re.findall(r'(\w+)=(?:([^=]+)(?:\s|$))+', line))
        for line in ispmgr_output
    ]


class SSLCache:

    @staticmethod
    def _call(*args, **kwargs):
        kwargs['check'] = True
        kwargs['encoding'] = 'utf-8'
        kwargs['stdout'] = PIPE
        stdout = run(['im360-ssl-cache', '--json', *args], **kwargs).stdout
        return json.loads(stdout) if stdout else None

    @classmethod
    def add(cls, data):
        cls._call('--add', '-', input=json.dumps(data))

    @classmethod
    def read(cls):
        return cls._call()

    @classmethod
    def remove(cls, data):
        cls._call('--remove', *data)


def collect_certs():
    result = []
    cert_data = ((entry['owner'], entry['name'], entry['info'])
                 for entry in get_ispmgr_data('sslcert'))
    for owner, cert_name, domains in cert_data:
        cert_dir = ISPMGR_CERTS_ROOT / owner
        priv_key = cert_dir / f'{cert_name}.key'
        cert = cert_dir / f'{cert_name}.crt'
        chain = cert_dir / f'{cert_name}.crtca'
        if priv_key.exists() and cert.exists():
            cert_data = cert.read_text()
            chain_data = chain.read_text() if chain.exists() else cert_data
            domain = domains.split(' ')[0]
            jsonline = {
                'domain': domain,
                'key': priv_key.read_text(),
                'certificate': cert_data,
                'chain': chain_data
            }
            result.append(jsonline)
    return result


def main():
    parsed_certs = collect_certs()
    parsed_domains = {entry['domain'] for entry in parsed_certs}
    cached_domains = set(SSLCache.read())
    domains_to_remove = cached_domains - parsed_domains
    SSLCache.remove(domains_to_remove)
    SSLCache.add(parsed_certs)


if __name__ == "__main__":
    sys.exit(main())
