# (C) Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
# 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 socket
import time

from monasca_agent.collector.checks.services_checks import ServicesCheck
from monasca_agent.collector.checks.services_checks import Status


class BadConfException(Exception):
    pass


class TCPCheck(ServicesCheck):

    @staticmethod
    def _load_conf(instance):
        # Fetches the conf

        port = instance.get('port', None)
        timeout = float(instance.get('timeout', 10))
        response_time = instance.get('collect_response_time', False)
        socket_type = None
        try:
            port = int(port)
        except Exception:
            raise BadConfException("%s is not a correct port." % str(port))

        try:
            url = instance.get('host', None)
            split = url.split(":")
        except Exception:  # Would be raised if url is not a string
            raise BadConfException("A valid url must be specified")

        # IPv6 address format: 2001:db8:85a3:8d3:1319:8a2e:370:7348
        if len(split) == 8:  # It may then be a IP V6 address, we check that
            for block in split:
                if len(block) != 4:
                    raise BadConfException("%s is not a correct IPv6 address." % url)

            addr = url
            # It's a correct IP V6 address
            socket_type = socket.AF_INET6

        if socket_type is None:
            try:
                addr = socket.gethostbyname(url)
                socket_type = socket.AF_INET
            except Exception:
                raise BadConfException("URL: %s is not a correct IPv4, IPv6 or hostname" % addr)

        return addr, port, socket_type, timeout, response_time

    def _check(self, instance):
        addr, port, socket_type, timeout, response_time = self._load_conf(instance)
        dimensions = self._set_dimensions(None, instance)
        if instance.get('host'):
            dimensions.update({'url': '{}:{}'.format(instance.get('host'),
                                                     port)})
        start = time.time()
        try:
            self.log.debug("Connecting to %s %s" % (addr, port))
            sock = socket.socket(socket_type)
            try:
                sock.settimeout(timeout)
                sock.connect((addr, port))
            finally:
                sock.close()

        except socket.timeout as e:
            # The connection timed out because it took more time than the specified
            # value in the yaml config file
            length = int((time.time() - start) * 1000)
            self.log.info("%s:%s is DOWN (%s). Connection failed after %s ms" %
                          (addr, port, str(e), length))
            return Status.DOWN, "%s. Connection failed after %s ms" % (str(e), length)

        except socket.error as e:
            length = int((time.time() - start) * 1000)
            if "timed out" in str(e):

                # The connection timed out because it took more time than the system tcp
                # stack allows
                self.log.warning(
                    "The connection timed out because it took more time than"
                    "the system tcp stack allows. You might want to change this"
                    "setting to allow longer timeouts")
                self.log.info("System tcp timeout. Assuming that the checked system is down")
                return Status.DOWN, """Socket error: %s.
                The connection timed out after %s ms
                because it took more time than the system tcp stack allows.
                You might want to change this setting to allow longer timeouts
                """ % (str(e), length)
            else:
                self.log.info("%s:%s is DOWN (%s). Connection failed after %s ms" %
                              (addr, port, str(e), length))
                return Status.DOWN, "%s. Connection failed after %s ms" % (str(e), length)

        except Exception as e:
            length = int((time.time() - start) * 1000)
            self.log.info("%s:%s is DOWN (%s). Connection failed after %s ms" %
                          (addr, port, str(e), length))
            return Status.DOWN, "%s. Connection failed after %s ms" % (str(e), length)

        if response_time:
            self.gauge('network.tcp.response_time', time.time() - start,
                       dimensions=dimensions)

        self.log.debug("%s:%s is UP" % (addr, port))
        return Status.UP, "UP"
