#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import json
import copy
import time

import search.tools.devops.libs.nanny_services as ns

from search.tools.devops.libs.modify_topology import modify_topology
from saas.tools.devops.lib23.ask_for_user_action import wait_for_manual_confirm


message_current_snapshot_is_not_active = '''
***
It seems that current snapshot is not active.
Probably that is because Marty have commited a release into the service and is going to activate his snapshot (that will rollback your changes).

Wait until they roll out the release or ask them to activate the latest snapshot.

Check the last snapshot in the service activated? (ok/no)?
'''

message_check_backends = '''
***
Check if the backends are up and ready. Here are some useful points:

    https://wiki.yandex-team.ru/users/yrum/fpoll/

    Probably useful #sky listinstances C@{conf_name} | fpoll -Envr OK status?brief=1

Do the backends seem ok (ok/no)?
'''

message_deploy_mmeta = '''
***
Now it is time for you to redeploy int/mmeta services with an updated config.
saas/tools/devops/update_mmeta_config may be useful here.
Have the ints/mmetas been deployed (ok/no)?
'''


def check_if_current_snapshot_is_target_active(service):
    service_attrs = ns.get_service(service).json()
    return service_attrs['runtime_attrs']['_id'] == service_attrs['target_state']['content']['snapshot_id']


def get_current_topology(service):
    response = ns.get_service_runtime_attrs(service)
    return response.json()["content"]["instances"]["extended_gencfg_groups"]["groups"]


# TBD : Need a better idea -- can hang forever by design
def wait_for_snaphsot_active(service, snapshot_id):
    logging.info('Waiting for snapshot %s activation', snapshot_id)
    target_snapshot_is_active = False
    while not target_snapshot_is_active:
        service_attrs = ns.get_service(service).json()
        for s in service_attrs['current_state']['content']['active_snapshots']:
            if s['state'] == 'ACTIVE' and s['snapshot_id'] == snapshot_id:
                logging.info('Snapshot %s activated', snapshot_id)
                target_snapshot_is_active = True
        if not target_snapshot_is_active:
            time.sleep(60)


def calculate_topology_modifiers(service, new_topology):

    current_topology = get_current_topology(service)
    #
    # Topology format here is:
    # [
    #   {
    #     'name':
    #     'release':
    #     'tags':
    #   }
    # ]

    assert new_topology is not None, "New topology must be provided"

    target_topology = []
    for t in current_topology:
        t_new = copy.deepcopy(t)
        t_new['release'] = new_topology
        if t_new not in target_topology:
            target_topology.append(t_new)

    logging.debug('Current topology : {}'.format(json.dumps(current_topology)))
    logging.debug('Target topology : {}'.format(json.dumps(target_topology)))

    topologies_to_add = []
    topologies_to_remove = []
    for t in target_topology + current_topology:
        if t in target_topology and t not in current_topology:
            topologies_to_add.append(t['release'])
        if t in current_topology and t not in target_topology:
            topologies_to_remove.append(t['release'])

    logging.debug('Calculated topologies to add : {}'.format(json.dumps(topologies_to_add)))
    logging.debug('Calculated topologies to remove : {}'.format(json.dumps(topologies_to_remove)))

    return topologies_to_add, topologies_to_remove


def recluster_phase_one(service, new_topology, dry_run=False, old_snapshot_check=True):

    (topologies_to_add, topologies_to_remove) = calculate_topology_modifiers(service, new_topology)

    if old_snapshot_check and not check_if_current_snapshot_is_target_active(service):
        wait_for_manual_confirm(message_current_snapshot_is_not_active)

    if topologies_to_add != []:
        logging.info("Adding topologies : {}".format(topologies_to_add))
        snapshot_id = modify_topology([service], topologies_to_add=topologies_to_add, topologies_to_remove=[], commit=True, print_diff=True)[service]

        if not dry_run:
            logging.info("Activating new snapshot #{}".format(snapshot_id))
            ns.set_active_snapshot_state(
                service,
                snapshot_id,
                recipe='common',
                prepare_recipe='common',
                comment="Adding topologies : {}".format(topologies_to_add),
            )

        return snapshot_id

    else:
        logging.error("No need to add topologies")
        service_attrs = ns.get_service(service).json()
        return service_attrs['runtime_attrs']['_id']


def recluster_phase_two(service, new_topology, dry_run=False, old_snapshot_check=True):

    (topologies_to_add, topologies_to_remove) = calculate_topology_modifiers(service, new_topology)

    if old_snapshot_check and not check_if_current_snapshot_is_target_active(service):
        wait_for_manual_confirm(message_current_snapshot_is_not_active)

    if topologies_to_remove != []:
        logging.info("Removing topologies : {}".format(topologies_to_remove))
        snapshot_id = modify_topology([service], topologies_to_add=[], topologies_to_remove=topologies_to_remove, commit=True, print_diff=True)[service]
        if not dry_run:
            logging.info("Activating new snapshot #{}".format(snapshot_id))
            ns.set_active_snapshot_state(
                service,
                snapshot_id,
                recipe='common',
                prepare_recipe='common',
                comment="Removing topologies : {}".format(topologies_to_remove),
                )

        return snapshot_id

    else:
        logging.error("No need to remove topologies")
        service_attrs = ns.get_service(service).json()
        return service_attrs['runtime_attrs']['_id']


def recluster_nanny_service(service, new_topology, two_phase=True, dry_run=False, old_snapshot_check=True):

    phase_one_dry_run = dry_run or not two_phase
    phase_two_dry_run = dry_run

    recluster_phase_one(service, new_topology, phase_one_dry_run, old_snapshot_check)
    if not phase_one_dry_run:
        wait_for_manual_confirm(message_check_backends)
        wait_for_manual_confirm(message_deploy_mmeta)

    recluster_phase_two(service, new_topology, phase_two_dry_run, old_snapshot_check)
    if not phase_two_dry_run:
        wait_for_manual_confirm(message_check_backends)
