"""
This script test if a Swift cluster ( or a swiftsore server)
is under replication
"""

import sys
import socket
import eventlet
from oslo_config import cfg
from swift.cli import recon
from swift.common.ring import RingBuilder
from urllib.parse import urlparse

# Initialize Swift connection
swiftrecon = recon.SwiftRecon()
pool_size = 30
pool = eventlet.GreenPool(pool_size)

CLI_OPTS = [
    cfg.StrOpt('type', short='t', choices=['object', 'container', 'account'],
               default='object', help='Choose de type of server'),
]


def _prepare_config():
    """
    Prepare the oslo_config of scripts by analyse arguments
    return: the oslo_config object
    """
    CONF = cfg.ConfigOpts()
    CONF.register_cli_opts(CLI_OPTS)
    return CONF


def analyze_ring():
    """
    Analyze the ring for retrieve informations about host or cluster
    If script running in a switstore, he check only this server,
    otherwise, he check all cluster
    return: list off tuble with ip and port, total partitions on this hosts
            parttion to be emptied ans filled
    """
    hosts = {}
    parts = 0
    filling = {}
    emptying = {}
    devs = [d for d in builder.devs if d]
    # If server have disk, use it, else use all swiftstore
    ip = socket.gethostbyname(socket.gethostname())
    devs_ip = [d for d in devs if d['ip'] == ip]
    if devs_ip:
        devs = devs_ip
    for h in devs:
        hosts[h['ip']] = h['port']
        parts += h['parts']
        if h['parts_wanted'] < -builder.parts:
            # If emtying swiftstore, parts-wanted is
            # (builder.parts * builder.replicas) -
            # total.parts to rebalance + device parts to rebalance
            # So is always < -builder.parts
            emptying.setdefault(h['ip'], 0)
            emptying[h['ip']] += h['parts']
        elif h['parts_wanted'] < 0:
            # If rebalance after add swifstore, this juste parts number
            # in swiftstore that they are emptied
            emptying.setdefault(h['ip'], 0)
            emptying[h['ip']] -= h['parts_wanted']
        elif h['parts_wanted'] > 0:
            # In all case this number of part of device that theyr are filled
            filling.setdefault(h['ip'], 0)
            filling[h['ip']] += h['parts_wanted']
    return set(hosts.items()), parts, filling, emptying


def get_attempted(hosts):
    """
    Analyze all hosts
    return: tuple with number of replication in progress
    """
    scout = recon.Scout(recon_type=f"replication/{args.type}",
                        suppress_errors=True)
    attempted = {}
    for url, response, status, _ts_start, _ts_end \
            in pool.imap(scout.scout, hosts):
        if status == 200:
            ip = urlparse(url).hostname
            attempted[ip] = response['replication_stats']['attempted']
    return sum(attempted.values())


def main():
    """
    Entry point for the script
    """
    global builder, args
    args = _prepare_config()
    try:
        args(sys.argv[1:])
    except cfg.RequiredOptError as E:
        print(E)
        args.print_usage()
        return 1

    try:
        builder = RingBuilder.load(f"/etc/swift/{args.type}.builder")
    except Exception as e:
        sys.exit(e)

    hosts, parts, filling, emptying = analyze_ring()
    total_emptying = sum(emptying.values())
    total_filling = sum(filling.values())
    percent_emptying = total_emptying / parts
    percent_filling = total_filling / parts
    if total_emptying or total_filling:
        print("Dispersion for the cluster is not optimal !")
        print(f"{total_filling} parts remains to be filled \
              ({percent_filling:.2%}) on {len(filling)} hosts")
        print(f"{total_emptying} parts remains to be emptied \
               ({percent_emptying:.2%}) on {len(emptying)} hosts")
        print("You can re-run 'swift-ring-builder object.builder rebalance' \
               AFTER the replication is OK !")
        print("=" * 80)

    attempted = get_attempted(hosts)
    inProgress = abs(attempted - parts)
    perCent = 1 - inProgress / parts
    if inProgress:
        print(f"Replication in progress: {inProgress} parts to sync \
              ({perCent:.2%} Ok) ")
    else:
        print("Replication OK")


if __name__ == "__main__":
    sys.exit(main())
