import logging
import time
import os

from twisted.internet import threads

from landscape.accumulate import Accumulator
from landscape.lib.monitor import CoverageMonitor
from landscape.lib.network import get_active_device_info
from landscape.monitor.plugin import MonitorPlugin

try:
    from swift.common.ring import Ring
    from swift.cli.recon import Scout
    has_swift = True
except ImportError:
    has_swift = False


class SwiftUsage(MonitorPlugin):
    """Plugin reporting Swift cluster usage.

    This only works if the client runs on a Swift node.  It requires the
    'python-swift' package to be installed (which is installed on swift nodes).

    """

    persist_name = "swift-usage"
    scope = "storage"

    def __init__(self, interval=30, monitor_interval=60 * 60,
                 create_time=time.time,
                 swift_ring="/etc/swift/object.ring.gz"):
        self._interval = interval
        self._monitor_interval = monitor_interval
        self._create_time = create_time
        self._swift_ring = swift_ring  # To discover Recon host/port

        self._has_swift = has_swift
        self._swift_usage_points = []
        self.active = True

    def register(self, registry):
        super(SwiftUsage, self).register(registry)
        self._accumulate = Accumulator(self._persist, self._interval)
        self._monitor = CoverageMonitor(
            self.run_interval, 0.8, "Swift device usage snapshot",
            create_time=self._create_time)
        self.registry.reactor.call_every(
            self._monitor_interval, self._monitor.log)

        self.registry.reactor.call_on("stop", self._monitor.log, priority=2000)
        self.call_on_accepted("swift-usage", self.send_message, True)

    def create_message(self):
        usage_points = self._swift_usage_points
        self._swift_usage_points = []
        if usage_points:
            return {"type": "swift-usage", "data-points": usage_points}

    def send_message(self, urgent=False):
        message = self.create_message()
        if message:
            self.registry.broker.send_message(
                message, self._session_id, urgent=urgent)

    def exchange(self, urgent=False):
        self.registry.broker.call_if_accepted(
            "swift-usage", self.send_message, urgent)

    def run(self):
        if not self._should_run():
            return

        self._monitor.ping()

        host = self._get_recon_host()
        deferred = threads.deferToThread(self._perform_recon_call, host)
        deferred.addCallback(self._handle_usage)
        return deferred

    def _should_run(self):
        """Return whether the plugin should run."""
        if not self.active:
            return False

        if not self._has_swift:
            logging.info(
                "This machine does not appear to be a Swift machine. "
                "Deactivating plugin.")
            self.active = False
            return False

        # Check for object ring config file.
        # If it is not present, it's not a Swift machine or it not yet set up.
        if not os.path.exists(self._swift_ring):
            return False

        return True

    def _get_recon_host(self):
        """Return a tuple with Recon (host, port)."""
        local_ips = self._get_local_ips()
        ring = Ring(self._swift_ring)
        for dev in ring.devs:
            if dev and dev["ip"] in local_ips:
                return dev["ip"], dev["port"]

    def _get_local_ips(self):
        """Return a list of IP addresses for local devices."""
        return [
            device["ip_address"] for device in get_active_device_info()]

    def _perform_recon_call(self, host):
        """Get usage information from Swift Recon service."""
        if not host:
            return

        scout = Scout("diskusage")
        # Perform the actual call
        _, disk_usage, code = scout.scout(host)
        if code == 200:
            return disk_usage

    def _handle_usage(self, disk_usage):
        timestamp = int(self._create_time())

        devices = set()
        for usage in disk_usage:
            if not usage["mounted"]:
                continue

            device = usage["device"]
            devices.add(device)

            step_values = []
            for key in ("size", "avail", "used"):
                # Store values in tree so it's easy to delete all values for a
                # device
                persist_key = "usage.%s.%s" % (device, key)
                step_value = self._accumulate(
                    timestamp, usage[key], persist_key)
                step_values.append(step_value)

            if all(step_values):
                point = [step_value[0], device]  # accumulated timestamp
                point.extend(int(step_value[1]) for step_value in step_values)
                self._swift_usage_points.append(tuple(point))

        # Update device list and remove usage for devices that no longer exist.
        current_devices = set(self._persist.get("devices", ()))
        for device in current_devices - devices:
            self._persist.remove("usage.%s" % device)
        self._persist.set("devices", list(devices))
