# -*- coding: utf-8 -*-

# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
import pwd
import sys
import subprocess
import socket
import time
import urllib.request as urllib2
from urllib.error import URLError
from .function import _print, get_ip_mac_type, parse_error
import OpenSSL
import hashlib
from .client_class import HTTPSClientCertTransport
from .cert_verify import VerifyError
from calculate.core.datavars import DataVarsCore
from calculate.core.server.methods_func import get_password
from calculate.lib.cl_lang import setLocalTranslate
from calculate.lib.utils.common import getpass
from calculate.lib.utils.files import listDirectory, readFile

_ = lambda x: x
setLocalTranslate('cl_console3', sys.modules[__name__])
VERSION = 0.11


def client_post_cert(client, clVars, show_info=False):
    """ send a certificate server for check """
    sid = client.get_sid()

    lang = os.environ['LANG'][:2]
    _result_post_cert, _result_sid = client.service.init_session(sid, lang)
    result_post_cert = _result_post_cert[1].integer
    result_sid = _result_sid[1].integer

    if os.environ.get("DEBUG"):
        print(_("The client uses certificate {certfile} "
                "(server ID {cert_id})").format(
            certfile=client.CERT_FILE, cert_id=result_post_cert[0]))
    if result_post_cert[0] == -4:
        print(_("Certificate not found on the server"))
        print(_("the client uses certificate %s") % client.CERT_FILE)
        print(_('You can generate a new certificate '
                'using options --gen-cert-by and '
                '--get-cert-from'))
        raise Exception(3)

    # client_sid(sid, client, cert_id = results[0][0], clVars = clVars)
    if result_post_cert[0] == -3:
        print(_("Certificate not sent!"))
    elif result_post_cert[0] == -2:
        print(_("Using the upstream certificate"))
    else:
        if show_info:
            print(_(" Your certifitate ID = %d") % (result_post_cert[0]))
        try:
            if result_post_cert[1] == -2:
                print(_("The certificate has expired"))
            elif result_post_cert[1] > 0:
                if show_info:
                    print(_("The certificate expires after %d days") % (
                        result_post_cert[1]))
        except:
            pass

    # work with sid
    client.write_sid(result_sid[0])

    if show_info:
        if result_sid[1] == 1:
            print(_(" New Session"))
        else:
            print(_(" Old Session"))
        print(_(" Your session ID = %s") % sid)


# Creation of secret key of the client
def new_key_req(key, cert_path, server_host_name, private_key_passwd=None,
                auto=False):
    from .create_cert import generateRSAKey, makePKey, makeRequest, \
        passphrase_callback

    rsa = generateRSAKey()
    rsa.save_key(key + '_pub', cipher=None, callback=lambda *unused: "")

    pkey = makePKey(rsa)
    if not passphrase_callback(private_key_passwd):
        pkey.save_key(key, cipher=None, callback=lambda *unused: "")
    else:
        pkey.save_key(key, callback=lambda *unused: str(private_key_passwd))

    req = makeRequest(rsa, pkey, server_host_name, auto)
    crtreq = req.as_pem()

    req_file = cert_path + '/%s.csr' % server_host_name
    crtfile = open(req_file, 'wb')
    crtfile.write(crtreq)
    crtfile.close()

    user_name = pwd.getpwuid(os.getuid()).pw_name
    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError as e:
        _print(parse_error(e))
        return None
    os.chown(key, pwdObj.pw_uid, pwdObj.pw_gid)
    os.chmod(key, 0o600)

    return req_file


def delete_old_cert(client):
    try:
        os.unlink(client.CERT_FILE)
        os.unlink(client.REQ_FILE)
        os.unlink(client.PKEY_FILE)
        os.unlink(client.PubKEY_FILE)
    except OSError as e:
        _print(parse_error(e))


def client_post_request(cert_path, args):
    if os.path.exists(cert_path + 'req_id'):
        print(_("You already sent a certificate signature request."))
        _print(_("Request ID = %s") % readFile(cert_path + 'req_id'))
        ans = input(_("Send a new request? y/[n]: "))
        if not ans.lower() in ['y', 'yes']:
            return 0
    clVars = DataVarsCore()
    clVars.importCore()
    clVars.flIniFile()

    port = args.port or clVars.Get('core.cl_core_port')

    url = "https://%s:%s/?wsdl" % (args.by_host, port)
    print('%s\n' % url, _("connecting..."))
    from .client_class import Client_suds

    try:
        client = Client_suds(url, transport=HTTPSClientCertTransport \
            (None, None, cert_path))
    except (KeyboardInterrupt, URLError) as e:
        print('\n' + _("Closing. Connection error."))
        _print(_("Error: %s") % e)
        return 0
    client.wsdl.services[0].setlocation(url)

    server_host_name = client.service.get_server_host_name()

    key = os.path.join(cert_path, server_host_name + '.key')
    csr_file = os.path.join(cert_path, server_host_name + '.csr')
    if os.path.exists(key) and os.path.exists(csr_file):
        print(_("the private key and request now exist"))
        ask = input(_("Create a new private key and request? y/[n]: "))
        if ask.lower() in ['y', 'yes']:
            passwd = get_password()
            new_key_req(key, cert_path, server_host_name,
                        private_key_passwd=passwd)
    else:
        passwd = get_password()
        new_key_req(key, cert_path, server_host_name,
                    private_key_passwd=passwd)

    ip, mac, client_type = get_ip_mac_type()
    data = readFile(csr_file)
    res = client.service.post_client_request(request=data, ip=ip,
                                             mac=mac, client_type=client_type)
    if int(res) < 0:
        print(_("The server has not signed the certificate!"))
        return 1
    fc = open(os.path.join(cert_path, 'req_id'), 'w')
    fc.write(res)
    fc.close()
    _print(_("Your request ID = %s") % res + '.\n',
           _("To submit the certificate request on the server use command") + \
           '\n' + 'cl-core --sign-client ID_CLIENT_REQUEST')
    return 0


def client_get_cert(cert_path, args):
    clVars = DataVarsCore()
    clVars.importCore()
    clVars.flIniFile()

    if not os.path.exists(os.path.join(cert_path, 'req_id')):
        print(_("Request not sent or file %s deleted") \
              % (os.path.join(cert_path, 'req_id')))
        return 1
    fc = open(os.path.join(cert_path, 'req_id'), 'r')
    req_id = fc.read()
    fc.close()

    port = args.port or clVars.Get('core.cl_core_port')
    url = "https://%s:%s/?wsdl" % (args.from_host, port)
    print('%s\n' % url, _("connecting..."))

    from .client_class import Client_suds

    try:
        client = Client_suds(url,
                             transport=HTTPSClientCertTransport(None, None,
                                                                cert_path))
    except KeyboardInterrupt:
        print(_("Closing. Connection error."))
        return 1
    client.wsdl.services[0].setlocation(url)

    server_host_name = client.service.get_server_host_name()

    if not os.path.exists(os.path.join(cert_path, server_host_name + '.csr')):
        print(_("Request %s not found on the client's side") \
              % (os.path.join(cert_path, server_host_name + '.csr')))
        return 1
    request = readFile(os.path.join(cert_path, server_host_name + '.csr'), binary=True)
    md5 = hashlib.md5()
    md5.update(request)
    md5sum = md5.hexdigest()

    result = client.service.get_client_cert(req_id, md5sum)
    cert = result[0][0]
    try:
        ca_root = result[0][1]
    except IndexError:
        ca_root = None
    if cert == '1':
        print(_("Signature request rejected!"))
        return 1
    elif cert == '2':
        print(_("Signature request not examined yet."))
        print(_("Your request ID = %s") % req_id + '.\n', \
            _("To submit the certificate request on the server use command") + \
            '\n' + 'cl-core --sign-client ID_CLIENT_REQUEST')
        return 1
    elif cert == '3':
        print(_("Request or signature not matching earlier data."))
        return 1
    elif cert == '4':
        print(_("The request was sent from another IP."))
        return 1
    cert_file = os.path.join(cert_path, server_host_name + '.crt')
    fc = open(cert_file, 'w')
    fc.write(cert)
    fc.close()
    try:
        os.unlink(cert_path + 'req_id')
    except OSError as e:
        _print(parse_error(e))
    print(_('Certificate saved. Your certificate ID: %s') % req_id)
    user_name = pwd.getpwuid(os.getuid()).pw_name
    try:
        pwdObj = pwd.getpwnam(user_name)
    except KeyError as e:
        _print(parse_error(e))
        return None
    os.chown(cert_file, pwdObj.pw_uid, pwdObj.pw_gid)
    os.chmod(cert_file, 0o600)

    if ca_root:
        system_ca_db = clVars.Get('core.cl_glob_root_cert')
        if os.path.exists(system_ca_db):
            if ca_root in readFile(system_ca_db):
                return 0

        cl_client_cert_dir = clVars.Get('core.cl_client_cert_dir')
        homePath = clVars.Get('ur_home_path')

        cl_client_cert_dir = cl_client_cert_dir.replace("~", homePath)
        root_cert_md5 = os.path.join(cl_client_cert_dir, "ca/cert_list")

        md5 = hashlib.md5()
        md5.update(ca_root.encode("UTF-8"))
        md5sum = md5.hexdigest()
        print("\n=================================================")
        print("md5sum = ", md5sum)

        if not os.path.exists(root_cert_md5):
            fc = open(root_cert_md5, "w")
            fc.close()

        filename = None
        with open(root_cert_md5) as fd:
            t = fd.read()
            # for each line
            for line in t.splitlines():
                # Split string into a words list
                words = line.split(' ', 1)
                if words[0] == md5sum:
                    filename = words[1]
        if not filename:
            certobj = OpenSSL.crypto.load_certificate \
                (OpenSSL.SSL.FILETYPE_PEM, ca_root)
            Issuer = certobj.get_issuer().get_components()
            for item in Issuer:
                if item[0] == b'CN':
                    filename = item[1].decode("UTF-8")

            fc = open(root_cert_md5, "a")
            fc.write('%s %s\n' % (md5sum, filename))
            fc.close()

            if not filename:
                print(_('Field "CN" not found in the certificate!'))
                return 1

            fd = open(os.path.join(cl_client_cert_dir, 'ca', filename), 'w')
            fd.write(ca_root)
            fd.close()

            user_root_cert = clVars.Get('core.cl_user_root_cert')
            user_root_cert = user_root_cert.replace("~", homePath)
            fa = open(user_root_cert, 'a')
            fa.write(ca_root)
            fa.close()
            print(_("filename = "), filename)
            print(_("Certificate added"))
        else:
            print(_("The file containing the CA certificate now exists"))
    return 0


def client_post_auth(client):
    """ authorization client or post request """
    sid = client.get_sid()
    client.sid = int(sid)
    try:
        if os.path.exists(client.CERT_FILE):
            pass  # client_post_cert(client)
        else:
            # client_post_request(client)
            print(_(
                "You do not have a certificate. Use option --gen-cert-by HOST to generate a new request or --get-cert-from HOST to get a new certificate from the server."))
            raise Exception(1)
            #        print client.service.versions(sid, VERSION)
    except VerifyError as e:
        print(e.value)
        raise Exception(1)


########## Get password
def getRunProc():
    """List run program"""

    def getCmd(procNum):
        cmdLineFile = '/proc/%s/cmdline' % procNum
        try:
            if os.path.exists(cmdLineFile):
                return [readFile(cmdLineFile).strip(), procNum]
        except:
            pass
        return ["", procNum]

    if not os.access('/proc', os.R_OK):
        return []
    return map(getCmd,
               list(filter(lambda x: x.isdigit(),
                      listDirectory('/proc'))))


def owner(pid):
    UID = 1
    for ln in open('/proc/%s/status' % pid):
        if ln.startswith('Uid:'):
            uid = int(ln.split()[UID])
            return pwd.getpwuid(uid).pw_name


def create_socket(file_path, username):
    host = ''  # ip
    port = 5501  # порт
    find_proc = False
    #    if not file_path:
    #        home_path = pwd.getpwuid(os.getuid()).pw_dir
    #        file_path = os.path.join(home_path, '.calculate', 'passwd_daemon')
    #    if not username:
    #        username = pwd.getpwuid(os.getuid()).pw_name

    for run_commands in filter(lambda x: 'cl-consoled' in \
            x[0], getRunProc()):
        if 'python' in run_commands[0]:
            if username == owner(run_commands[1]):
                # print 'YES'
                find_proc = True

    if not find_proc:
        try:
            os.unlink(file_path)
        except OSError as e:
            _print(parse_error(e))

        cmd = ['cl-consoled']
        # print cmd
        subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    while True:
        try:
            s.bind((host, port))  # ассоциировать адрес с сокетом
            break
        except socket.error:
            port += 1
    return s


def set_password(s, req, size):
    password = getpass.getpass(_('Password: '))
    msg = '%s,%s' % (req, password)
    s.send(msg)
    resp = s.recv(size)
    if resp.startswith('Error'):
        _print(resp)
    return password


def clear_password(server_host, server_port):
    size = 1024  # размер данных
    username = pwd.getpwuid(os.getuid()).pw_name

    home_path = pwd.getpwuid(os.getuid()).pw_dir
    file_path = os.path.join(home_path, '.calculate', 'passwd_daemon')

    s = create_socket(file_path, username)

    connect_error = 0
    while connect_error < 16:
        try:
            while connect_error < 10:
                if os.path.isfile(file_path):
                    serv_port, hash_val = readFile(file_path).split()
                    s.connect(('localhost', int(serv_port)))
                    req = 'delete,%s,%s,%s,%s' % (server_host, str(server_port),
                                                  username, hash_val)
                    s.send(req)
                    s.recv(size)
                    return
                else:
                    connect_error += 1
                    time.sleep(0.3)
            break
        except socket.error:
            time.sleep(0.3)


def socket_connect(s, file_path):
    connect_error = 0
    while connect_error < 16:
        try:
            while connect_error < 10:
                if os.path.isfile(file_path):
                    serv_port, hash_val = readFile(file_path).split()
                    s.connect(('localhost', int(serv_port)))
                    return s, hash_val
                else:
                    connect_error += 1
                    time.sleep(0.3)
            break
        except socket.error:
            time.sleep(0.3)


def get_password_from_daemon(server_host, server_port, wait_thread):
    size = 1024  # размер данных
    username = pwd.getpwuid(os.getuid()).pw_name

    home_path = pwd.getpwuid(os.getuid()).pw_dir
    file_path = os.path.join(home_path, '.calculate', 'passwd_daemon')

    while True:
        s = create_socket(file_path, username)

        s, hash_val = socket_connect(s, file_path)
        req = '%s,%s,%s,%s' % (
            server_host, str(server_port), username, hash_val)
        s.send(req)
        resp = s.recv(size)
        if resp.startswith('Error'):
            if 'timeout' in resp:
                continue
            wait_thread.stop()
            sys.stdout.write('\r')
            sys.stdout.flush()
            password = set_password(s, req, size)
        else:
            password = resp if resp else None
        return password
