# Copyright (c) 2015 iXsystems, Inc.
# All rights reserved.
# This file is a part of TrueNAS
# and may not be copied and/or distributed
# without the express permission of iXsystems.

import base64
import errno
import json
import logging
import os
import requests
import signal
import time

from freenasUI.middleware.client import client, ClientException
from requests_toolbelt.multipart.encoder import MultipartEncoder

log = logging.getLogger('freenasUI.failover.notifier')

INTERNAL_IFACE_NF = '/tmp/.failover_internal_iface_not_found'


class FailoverNotifier(object):

    def restart_hasyncd(self):
        try:
            with open('/var/run/hasyncd.pid', 'r') as f:
                pid = f.read()
            os.kill(int(pid), signal.SIGTERM)
            time.sleep(0.2)
            os.kill(int(pid), signal.SIGKILL)
        except:
            pass
        self._system_nolog(
            "/usr/local/bin/python /usr/local/www/freenasUI/tools/hasyncd.py"
        )

    def failover_licensed(self):
        with client as c:
            return c.call('failover.licensed')

    def failover_status(self):
        """
        Return the current status of this node in the failover

        Returns:
            MASTER
            BACKUP
            ELECTING
            IMPORTING
            ERROR
            SINGLE
        """
        with client as c:
            return c.call('failover.status')

    def failover_hardware(self):
        from freenasUI.failover.detect import ha_hardware
        return ha_hardware()

    def failover_internal_interfaces(self):
        # XXX: Use in migrations:
        # failover/migrations/0014
        # XXX: DO NOT REMOVE
        hardware = self.failover_hardware()
        if hardware == 'ECHOSTREAM':
            proc = self._pipeopen('/usr/sbin/pciconf -lv | grep "card=0xa01f8086 chip=0x10d38086"', logger=None)
            output = proc.communicate()[0]
            if not output:
                if not os.path.exists(INTERNAL_IFACE_NF):
                    open(INTERNAL_IFACE_NF, 'w').close()
                return []
            return [output.split('@')[0]]
        elif hardware == 'SBB':
            return ['ix0']
        elif hardware == 'ECHOWARP' or hardware == 'PUMA':
            return ['ntb0']
        elif hardware == 'ULTIMATE':
            return ['igb1']
        elif hardware == 'BHYVE':
            return ['em0']
        return []

    def failover_node(self):
        from freenasUI.failover.detect import ha_node
        # XXX: Use in migrations:
        # failover/migrations/0016
        # XXX: DO NOT REMOVE/CHANGE
        node = ha_node()
        if node is None:
            return 'MANUAL'
        return node

    def failover_pair_ip(self):
        node = self.failover_node()
        if node == 'MANUAL':
            #c, conn = self._open_db()
            #c.execute(
            #    'SELECT '
            #    '   ipaddress '
            #    'FROM system_failover '
            #    'WHERE disabled = 0 '
            #    'ORDER BY -id LIMIT 1'
            #)
            #ipaddress = c.fetchone()
            #c.close()
            #conn.close()
            return None
        elif node == 'A':
            return '169.254.10.2'
        elif node == 'B':
            return '169.254.10.1'
        else:
            return None

    def failover_force_master(self):
        with client as c:
            return c.call('failover.force_master')

    def failover_rpc(self, ip=None, timeout=3):
        from freenasUI.failover.utils import TimeoutTransport, ServerProxy
        if ip is None:
            ip = self.failover_pair_ip()
        if ip is None:
            return None
        s = ServerProxy(
            'http://%s:8000' % ip,
            allow_none=True,
            transport=TimeoutTransport(conntimeout=timeout),
        )
        return s

    def sync_file_recv(self, c, path, dst=None):
        data = c.call('failover.call_remote', 'filesystem.file_get_contents', [path])
        if data is None:
            return None
        if dst is None:
            dst = path
        with open(dst, 'wb+') as f:
            f.write(base64.b64decode(data))
        stat = c.call('failover.call_remote', 'filesystem.stat', [path])
        os.chmod(dst, stat['mode'])

    def sync_file_send(self, c, path, dest=None, legacy=False):
        if dest is None:
            dest = path
        if os.path.exists(path):
            mode = os.stat(path).st_mode
            with open(path, 'rb') as f:
                first = True
                while True:
                    read = f.read(1024 * 1024 * 20)
                    if not read:
                        break
                    if legacy:
                        c.file_send(dest, base64.b64encode(read).decode(), mode, not first)
                    else:
                        c.call('failover.call_remote', 'filesystem.file_receive', [dest, base64.b64encode(read).decode(), {'mode': mode, 'append': not first}])
                    first = False

    def sync_file_send_v2(self, c, path, dest=None):
        if dest is None:
            dest = path
        if os.path.exists(path):
            mode = os.stat(path).st_mode
        token = c.call('failover.call_remote', 'auth.generate_token')

        m = MultipartEncoder(fields={
            'data': json.dumps({'method': 'filesystem.put', 'params': [dest, {'mode': mode}]}),
            'file': open(path, 'rb'),
        })
        r = requests.post(
            f'http://{self.failover_pair_ip()}:6000/_upload/?auth_token={token}',
            data=m,
            headers={'Content-Type': m.content_type}
        )
        if r.status_code != 200:
            raise ValueError(f'{r.status_code}: {r.text}')

    def failover_sync_peer(self, fromto='to'):
        '''
        Runs synctopeer script

        Returns:
            False - No failover configured
        Raises:
            ValueError
        '''

        try:
            with client as c:
                if fromto == 'to':
                    c.call('failover.sync_to_peer')
                else:
                    c.call('failover.sync_from_peer')
        except Exception as e:
            raise ValueError('Sync %s peer failed: %s' % (fromto, e))
        return True

    def failover_disabled_reasons(self):
        with client as c:
            return c.call('failover.disabled_reasons')
