/*
 * Copyright 2016-present Open Networking Laboratory
 *
 * 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.
 */
package org.opencord.cordvtn.cli;

import com.jcraft.jsch.Session;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.packet.IpAddress;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.opencord.cordvtn.api.node.CordVtnNode;
import org.opencord.cordvtn.api.node.CordVtnNodeService;
import org.onosproject.net.Device;
import org.onosproject.net.device.DeviceService;

import java.util.Set;

import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.opencord.cordvtn.api.Constants.*;
import static org.opencord.cordvtn.impl.RemoteIpCommandUtil.*;

/**
 * Checks detailed node init state.
 */
@Command(scope = "onos", name = "cordvtn-node-check",
        description = "Shows detailed node init state")
public class CordVtnNodeCheckCommand extends AbstractShellCommand {

    @Argument(index = 0, name = "hostname", description = "Hostname",
            required = true, multiValued = false)
    private String hostname = null;

    private static final String HINT = "hint: try init again if the state is not" +
            " COMPLETE but all settings are OK";

    @Override
    protected void execute() {
        CordVtnNodeService nodeService = AbstractShellCommand.get(CordVtnNodeService.class);
        DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);

        CordVtnNode node = nodeService.nodes().stream()
                .filter(n -> n.hostname().equals(hostname))
                .findFirst()
                .orElse(null);

        if (node == null) {
            print("Cannot find %s from registered nodes", hostname);
            return;
        }
        print("Current state: %s (%s)", node.state().name(), HINT);
        print("%n[Integration Bridge Status]");
        Device device = deviceService.getDevice(node.integrationBridgeId());
        if (device != null) {
            print("%s %s=%s available=%s %s",
                  deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
                  INTEGRATION_BRIDGE,
                  device.id(),
                  deviceService.isAvailable(device.id()),
                  device.annotations());

            node.systemInterfaces().forEach(iface -> print(
                    getPortState(deviceService, node.integrationBridgeId(), iface)));
        } else {
            print("%s %s=%s is not available",
                  MSG_NO,
                  INTEGRATION_BRIDGE,
                  node.integrationBridgeId());
        }

        print("%n[Interfaces and IP setup]");
        Session session = connect(node.sshInfo());
        if (session != null) {
            Set<IpAddress> ips = getCurrentIps(session, INTEGRATION_BRIDGE);
            boolean isUp = isInterfaceUp(session, INTEGRATION_BRIDGE);
            boolean isIp = ips.contains(node.dataIp().ip()) &&
                    ips.contains(node.localManagementIp().ip());

            print("%s %s up=%s Ips=%s",
                  isUp && isIp ? MSG_OK : MSG_NO,
                  INTEGRATION_BRIDGE,
                  isUp ? Boolean.TRUE : Boolean.FALSE,
                  getCurrentIps(session, INTEGRATION_BRIDGE));

            print(getSystemIfaceState(session, node.dataInterface()));
            if (node.hostManagementInterface() != null) {
                print(getSystemIfaceState(session, node.hostManagementInterface()));
            }

            disconnect(session);
        } else {
            print("%s Unable to SSH to %s", MSG_NO, node.hostname());
        }
    }

    private String getPortState(DeviceService deviceService, DeviceId deviceId,
                                String portName) {
        Port port = deviceService.getPorts(deviceId).stream()
                .filter(p -> p.annotations().value(PORT_NAME).equals(portName) &&
                        p.isEnabled())
                .findAny().orElse(null);

        if (port != null) {
            return String.format("%s %s portNum=%s enabled=%s %s",
                    port.isEnabled() ? MSG_OK : MSG_NO,
                    portName,
                    port.number(),
                    port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
                    port.annotations());
        } else {
            return String.format("%s %s does not exist", MSG_NO, portName);
        }
    }

    private String getSystemIfaceState(Session session, String iface) {
        boolean isUp = isInterfaceUp(session, iface);
        boolean isIp = getCurrentIps(session, iface).isEmpty();
        return String.format("%s %s up=%s IpFlushed=%s",
              isUp && isIp ? MSG_OK : MSG_NO,
              iface,
              isUp ? Boolean.TRUE : Boolean.FALSE,
              isIp ? Boolean.TRUE : Boolean.FALSE);
    }
}
