package consul

import (
	"errors"
)

// ConnectProxies implements SupportedProxiesAPI by using the Consul Agent API.
type ConnectProxies struct {
	agentAPI AgentAPI
}

func NewConnectProxiesClient(agentAPI AgentAPI) *ConnectProxies {
	return &ConnectProxies{
		agentAPI: agentAPI,
	}
}

// Proxies returns a map of the supported proxies. The proxies are sorted from
// Consul with the most preferred version as the 0th element.
//
// If Consul is of a version that does not support the API, a nil map is returned
// with no error.
//
// If Consul cannot be reached an error is returned.
func (c *ConnectProxies) Proxies() (map[string][]string, error) {
	// Based on the Consul query:
	// $ curl -s localhost:8500/v1/agent/self | jq .xDS
	// {
	//  "SupportedProxies": {
	//    "envoy": [
	//      "1.15.0",
	//      "1.14.4",
	//      "1.13.4",
	//      "1.12.6"
	//    ]
	//  }
	// }

	self, err := c.agentAPI.Self()
	if err != nil {
		// this should not fail as long as we can reach consul
		return nil, err
	}

	// If consul does not return a map of the supported consul proxies, it
	// must be a version from before when the API was added in versions
	// 1.9.0, 1.8.3, 1.7.7. Earlier versions in the same point release as well
	// as all of 1.6.X support Connect, but not the supported proxies API.
	// For these cases, we can simply fallback to the old version of Envoy
	// that Nomad defaulted to back then - but not in this logic. Instead,
	// return nil so we can choose what to do at the caller.

	xds, xdsExists := self["xDS"]
	if !xdsExists {
		return nil, nil
	}

	proxies, proxiesExists := xds["SupportedProxies"]
	if !proxiesExists {
		return nil, nil
	}

	// convert interface{} to map[string]interface{}

	intermediate, ok := proxies.(map[string]interface{})
	if !ok {
		return nil, errors.New("unexpected SupportedProxies response format from Consul")
	}

	// convert map[string]interface{} to map[string][]string

	result := make(map[string][]string, len(intermediate))
	for k, v := range intermediate {

		// convert interface{} to []interface{}

		if si, ok := v.([]interface{}); ok {
			ss := make([]string, 0, len(si))
			for _, z := range si {

				// convert interface{} to string

				if s, ok := z.(string); ok {
					ss = append(ss, s)
				}
			}
			result[k] = ss
		}
	}

	return result, nil
}
