import threading
import queue
from datetime import datetime as dt
import subprocess
import shlex
import shutil
import traceback
import time
import json

from . import rc


class FileOpsManager(threading.Thread):

    def __init__(self, crypt_path, console_encoding, timeout):
        super().__init__()
        if not crypt_path.is_file():
            msg = 'Программа cryprcp (%s) не найдена'
            raise CryptPathError(msg % crypt_path)
        self.que_fwd = queue.Queue()
        self.que_bkwd = queue.Queue()
        self.crypt_path = crypt_path
        self.console_encoding = console_encoding
        self.timeout = timeout

    def _make_cmd(self, crypt_path, crypt_args, sha1, fpath):
        crypt_cmd = '"%s" %s' % (crypt_path, crypt_args)
        return crypt_cmd.format(sha1=sha1, f=str(fpath))

    def _make_json(self, fpath, sign_path, personal_data, json_path):
        sign_dt = dt.fromtimestamp(sign_path.stat().st_mtime)
        d = {fpath.name: {'time': sign_dt.strftime(rc.dtfmt)}}
        d[fpath.name].update(personal_data)
        with open(json_path, 'w', encoding='utf-8') as jsonfile:
            json.dump(d, jsonfile, ensure_ascii=False, indent=4)

    def sign_file(self, crypt_args, pers_data, fpath, psign_path, sign_path,
                  json_path):
        sha1 = pers_data['sha1']
        cmd = self._make_cmd(self.crypt_path, crypt_args, sha1, fpath)
        self.que_fwd.put(cmd)
        timeout_occured = False
        t0 = time.perf_counter()
        while True:
            try:
                retcode, stdout, stderr = self.que_bkwd.get(block=False)
            except queue.Empty:
                if time.perf_counter() - t0 > self.timeout:
                    if not timeout_occured:
                        self.subproc.kill()
                    timeout_occured = True
                time.sleep(0.01)
            else:
                break
        if retcode or timeout_occured:
            self._errinfo_to_log(cmd, retcode, stdout, stderr,
                                 timeout_occured)
            return 1
        else:
            psign_path.rename(sign_path)
            self._make_json(fpath, sign_path, pers_data, json_path)
            return 0

    def check_sign(self, crypt_args, sha1, fpath, psign_path, sign_path,
                   json_path):
        shutil.copy(sign_path, psign_path)
        cmd = self._make_cmd(self.crypt_path, crypt_args, sha1, fpath)
        self.que_fwd.put(cmd)
        timeout_occured = False
        t0 = time.perf_counter()
        while True:
            try:
                retcode, stdout, stderr = self.que_bkwd.get(block=False)
            except queue.Empty:
                if time.perf_counter() - t0 > self.timeout:
                    if not timeout_occured:
                        self.subproc.kill()
                    timeout_occured = True
                time.sleep(0.01)
            else:
                break
        psign_path.unlink()
        if retcode or timeout_occured:
            self._errinfo_to_log(cmd, retcode, stdout, stderr,
                                 timeout_occured)
            return 1
        else:
            try:
                with open(json_path, 'r', encoding='utf-8') as jsonfile:
                    json_dict = json.load(jsonfile)
                for k in ("time", "name", "post", "sha1"):
                    json_dict[fpath.name][k]
            except:
                return 1
            else:
                return 0

    def run(self):
        for cmd in iter(self.que_fwd.get, 'STOP'):
            retcode, stdout, stderr = self._fileop(cmd, self.console_encoding)
            self.que_bkwd.put((retcode, stdout, stderr))

    def finish(self):
        self.que_fwd.put('STOP')
        time.sleep(0.1)
        self.join()

    def _fileop(self, cmd, console_encoding):
        self.subproc = subprocess.Popen(shlex.split(cmd),
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        shell=False,
                                        universal_newlines=False)
        stdout_bytes, stderr_bytes = self.subproc.communicate()
        retcode = self.subproc.poll()
        return (retcode,
                stdout_bytes.decode(console_encoding).rstrip(),
                stderr_bytes.decode(console_encoding).rstrip())

    def _errinfo_to_log(self, cmd, retcode, stdout, stderr, timeout_occured):
        now = dt.now().strftime('%Y%m%d_%H%M%S')
        fpath = rc.fileop_err_path.format(datetime=now)
        log = []
        log.append('cmd:\n\t%s\n' % cmd)
        log.append('retcode:\n\t%s\n' % retcode)
        log.append('timeout_occured:\n\t%s\n' % timeout_occured)
        log.append('stdout:\n\t%s\n' % stdout)
        log.append('stderr:\n\t%s\n' % stderr)
        write_traceback(path=fpath, msg='\n'.join(log))


class CryptPathError(Exception):
    """ Ошибка пути к программе cryptcp. """
    pass


class TimeoutError(Exception):
    """ Таймаут файловой операции. """
    pass


class RetcodeError(Exception):
    """ Ошибка программы cryptcp. """
    pass


def write_traceback(path=rc.traceback_path, msg=None):
    if msg is None:
        msg = traceback.format_exc()
    with open(path, 'w', encoding='utf-8', newline='\r\n') as tbf:
        tbf.write(msg)
    return path
