/*
© Copyright IBM Corporation 2021

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 main

import (
	"context"
	"fmt"
	"path/filepath"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
)

const defaultHAPort = 9414

// HAReplicaStatus represents the Active/Replica/Replica container status of the queue manager
type HAReplicaStatus struct {
	Active  string
	Replica [2]string
}

func createBridgeNetwork(cli *client.Client, t *testing.T) (types.NetworkCreateResponse, error) {
	return cli.NetworkCreate(context.Background(), t.Name(), types.NetworkCreate{})
}

func removeBridgeNetwork(cli *client.Client, networkID string) error {
	return cli.NetworkRemove(context.Background(), networkID)
}

func getNativeHAContainerConfig(containerName string, replicaNames [3]string, haPort int) container.Config {
	return container.Config{
		Env: []string{
			"LICENSE=accept",
			"MQ_QMGR_NAME=QM1",
			"AMQ_CLOUD_PAK=true",
			"MQ_NATIVE_HA=true",
			fmt.Sprintf("HOSTNAME=%s", containerName),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_NAME=%s", replicaNames[0]),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_NAME=%s", replicaNames[1]),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_NAME=%s", replicaNames[2]),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_REPLICATION_ADDRESS=%s(%d)", replicaNames[0], haPort),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_REPLICATION_ADDRESS=%s(%d)", replicaNames[1], haPort),
			fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_REPLICATION_ADDRESS=%s(%d)", replicaNames[2], haPort),
		},
	}
}

func getNativeHASecureHostConfig(t *testing.T) container.HostConfig {
	return container.HostConfig{
		Binds: []string{
			coverageBind(t),
			filepath.Join(getCwd(t, true), "../tls") + ":/etc/mqm/ha/pki/keys/ha",
		},
	}
}

func getNativeHANetworkConfig(networkID string) network.NetworkingConfig {
	return network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			networkID: &network.EndpointSettings{},
		},
	}
}

func getActiveReplicaInstances(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) (HAReplicaStatus, error) {

	var actives []string
	var replicas []string

	for _, id := range qmReplicaIDs {
		qmReplicaStatus := getQueueManagerStatus(t, cli, id, "QM1")
		if qmReplicaStatus == "Running" {
			actives = append(actives, id)
		} else if qmReplicaStatus == "Replica" {
			replicas = append(replicas, id)
		} else {
			err := fmt.Errorf("Expected status to be Running or Replica, got status: %s", qmReplicaStatus)
			return HAReplicaStatus{}, err
		}
	}

	if len(actives) != 1 || len(replicas) != 2 {
		err := fmt.Errorf("Expected 1 Active and 2 Replicas, got: %d Active and %d Replica", len(actives), len(replicas))
		return HAReplicaStatus{}, err
	}

	return HAReplicaStatus{actives[0], [2]string{replicas[0], replicas[1]}}, nil
}

func waitForReadyHA(t *testing.T, cli *client.Client, qmReplicaIDs [3]string) {

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	for {
		select {
		case <-time.After(1 * time.Second):
			for _, id := range qmReplicaIDs {
				rc, _ := execContainer(t, cli, id, "", []string{"chkmqready"})
				if rc == 0 {
					t.Log("MQ is ready")
					rc, _ := execContainer(t, cli, id, "", []string{"chkmqstarted"})
					if rc == 0 {
						t.Log("MQ has started")
						return
					}
				}
			}
		case <-ctx.Done():
			t.Fatal("Timed out waiting for HA Queue Manager to become ready")
		}
	}
}

func waitForFailoverHA(t *testing.T, cli *client.Client, replicas [2]string) {

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	for {
		select {
		case <-time.After(1 * time.Second):
			for _, id := range replicas {
				if status := getQueueManagerStatus(t, cli, id, "QM1"); status == "Running" {
					return
				}
			}
		case <-ctx.Done():
			t.Fatal("Timed out waiting for Native HA Queue Manager to failover to an available replica")
		}
	}
}
