import os
import re
from subprocess import Popen, PIPE
import getpass

from django.core.exceptions import ObjectDoesNotExist

from staff.lib.exceptions import ErrorWithStatusCode

from staff.person.models import Staff
from staff.person_profile.errors import log_does_not_exist_staff_login
from staff.keys.models import GPGKey
from staff.keys.audit import log_gpg_key_updating

import logging
logger = logging.getLogger('person_profile.controllers.gpg_key')


class GPGKeyError(ErrorWithStatusCode):
    pass


class GPGKeyManyKeysError(GPGKeyError):
    pass


class GPGKeyCannotRunError(GPGKeyError):
    pass


class GPGKeyRunningError(GPGKeyError):
    pass


class GPGKeyNoFingerprintError(GPGKeyError):
    pass


def get_initial(login):
    return (
        GPGKey.objects
        .values(
            'id',
            'key',
            'description',
            'fingerprint',
        )
        .filter(staff__login=login)
        .order_by('id')
    )


def get_key(login, id):
    try:
        key = GPGKey.objects.get(id=id)
    except ObjectDoesNotExist as e:
        raise GPGKeyError(e)

    if not key.staff.login == login:
        # TODO добавить объект ошибки
        raise GPGKeyError()

    return key.key


def update(login, data, author_login, ip='0.0.0.0'):
    with log_does_not_exist_staff_login(logger=logger, message_params=[login], raise_e=GPGKeyError):
        staff_id = Staff.objects.values_list('id', flat=True).get(login=login)

    old_objects = {o.id: o for o in GPGKey.objects.filter(staff_id=staff_id)}

    for key in data:
        pk = key.get('id')
        description = key.get('description', '')

        try:
            if pk is not None:
                gpg_key = old_objects.pop(pk)
                if gpg_key.description != description:
                    gpg_key.description = description
                    gpg_key.save(force_update=True)
            else:
                GPGKey(**{
                    'staff_id': staff_id,
                    'key': key['key'],
                    'description': description,
                    'fingerprint': key['fingerprint'],
                }).save(force_insert=True)
                log_gpg_key_updating(
                    action='added',
                    fingerprint=key['fingerprint'],
                    fingerprint_sha256='none',
                    author_login=author_login,
                    owner_login=login,
                    ip=ip,
                )
        except Exception as e:
            logger.exception('Cannot save GPG key: %s', key)
            raise GPGKeyError(e)

    if old_objects:
        for old_key in old_objects.values():
            try:
                old_key.delete()
                log_gpg_key_updating(
                    action='removed',
                    fingerprint=old_key.fingerprint,
                    fingerprint_sha256='none',
                    author_login=author_login,
                    owner_login=login,
                    ip=ip,
                )
            except ObjectDoesNotExist as e:
                logger.exception('Cannot delete GPG key: %s', old_key)
                raise GPGKeyError(e)


def gpg_fingerprint(key):
    try:
        cmd = (
            'gpg',
            '--with-fingerprint',
            '--homedir=' + _create_gpg_dir(),
            '--no-permission-warning',
            '--quiet',
            '--with-colons',
        )
        proc = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        stdout, stderr = proc.communicate(key.encode('utf-8'))
    except Exception as e:
        raise GPGKeyCannotRunError(e)
    if stderr:
        raise GPGKeyRunningError(stderr.decode('utf-8'))
    if stdout:
        stdout = stdout.decode('utf-8')
        was_pub = False
        fingerprint = ''
        for row in stdout.split('\n'):
            if row.startswith('pub'):
                if was_pub:
                    raise GPGKeyManyKeysError(stdout)
                was_pub = True
            elif row.startswith('fpr') and was_pub and not fingerprint:
                fingerprint = re.sub(r'fpr|:', '', row)

        if fingerprint:
            return fingerprint

        raise GPGKeyNoFingerprintError(stdout)

    raise GPGKeyNoFingerprintError('No output')


GPG_DIR = '/tmp/tools-staff4-gpg-%s/' % getpass.getuser()


def _create_gpg_dir():
    if not os.path.exists(GPG_DIR):
        os.makedirs(GPG_DIR)
    return GPG_DIR
