/*
 * Copyright (C) 2017-2018 "IoT.bzh"
 * Author Sebastien Douheret <sebastien@iot.bzh>
 *
 * 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 agent

import (
	"fmt"
	"io"
	"strings"
	"time"

	common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
	uuid "github.com/satori/go.uuid"
)

// XdsSupervisor .
type XdsSupervisor struct {
	*Context
	ID        string
	BaseURL   string
	ConnRetry int
	Connected bool
	Disabled  bool

	// Private fields
	client      *common.HTTPClient
	logOut      io.Writer
	cbOnConnect OnConnectedXdsSupervCB
}

// XdsSuperVRequest Resquest field of a reply
type XdsSuperVRequest struct {
	Status string `json:"status"`
	Info   string `json:"info"`
}

// XdsSuperVReply Reply structure of XDS Supervision Daemon
type XdsSuperVReply struct {
	JType    string           `json:"jtype"`
	Request  XdsSuperVRequest `json:"request"`
	Response interface{}      `json:"response"`
}

// XdsSuperVTraceConfig
type XdsSuperVTraceConfig struct {
	Pid    int    `json:"pid"`
	Pids   []int  `json:"pids"`
	WsName string `json:"ws"`
}

// OnConnectedXdsSupervCB connect callback
type OnConnectedXdsSupervCB func(svr *XdsSupervisor) error

// NewXdsSupervisor creates an instance of XdsSupervisor
func NewXdsSupervisor(ctx *Context) *XdsSupervisor {
	return &XdsSupervisor{
		Context:   ctx,
		ID:        "XdsSupervisor-" + uuid.NewV1().String(),
		BaseURL:   ctx.Config.FileConf.ProfileConf.XDSBinder.URL,
		ConnRetry: ctx.Config.FileConf.ProfileConf.XDSBinder.ConnRetry,
		Connected: false,
		Disabled:  false,

		logOut: ctx.Log.Out,
	}
}

// Connect Establish HTTP connection with XDS Supervisor Dameon
func (xs *XdsSupervisor) Connect() error {
	var err error
	var retry int

	xs.Disabled = false
	xs.Connected = false

	err = nil
	for retry = xs.ConnRetry; retry > 0; retry-- {
		if err = xs._CreateConnectHTTP(); err == nil {
			break
		}
		if retry == xs.ConnRetry {
			// Notify only on the first conn error
			// doing that avoid 2 notifs (conn false; conn true) on startup
			xs._NotifyState()
		}
		xs.Log.Infof("Establishing connection to XDS Supervisor daemon (retry %d/%d)", retry, xs.ConnRetry)
		time.Sleep(time.Second)
	}
	if retry == 0 {
		// FIXME: re-use _Reconnect to wait longer in background
		return fmt.Errorf("Connection to XDS Supervisor daemon failure")
	}
	if err != nil {
		return err
	}

	// Check HTTP connection and establish WS connection
	err = xs._Connect(false)

	return err
}

// ConnectOn Register a callback on events reception
func (xs *XdsSupervisor) ConnectOn(f OnConnectedXdsSupervCB) error {
	xs.cbOnConnect = f
	return nil
}

// GetVersion Send Get request to retrieve XDS Supervision version
func (xs *XdsSupervisor) GetVersion(res interface{}) error {
	// FIXME add suffix URLSuffix in common HTTP client lib instead of _BuildURL
	return xs.client.Get(xs._BuildURL("/version"), &res)
}

// GetTopo Send Get request to retrieve Services/Daemons topology
func (xs *XdsSupervisor) GetTopo(res interface{}) error {
	return xs.client.Get(xs._BuildURL("/list"), &res)
}

// StartTrace Send Supervisor config and start tracing
func (xs *XdsSupervisor) StartTrace(cfg XdsSuperVTraceConfig, res interface{}) error {
	return xs.client.Post(xs._BuildURL("/trace/start"), cfg, &res)
}

// StopTrace Send Supervisor stop tracing
func (xs *XdsSupervisor) StopTrace(res interface{}) error {
	var cfg interface{}
	return xs.client.Post(xs._BuildURL("/trace/stop"), cfg, res)
}

/***
** Private functions
***/

// _BuildURL .
func (xs *XdsSupervisor) _BuildURL(url string) string {
	return url + "?token=HELLO&uuid=magic"
}

// Create HTTP client
func (xs *XdsSupervisor) _CreateConnectHTTP() error {
	var err error
	// FIXME SEB - Client key not in header but in cookie
	// temporary workaround: used _BuildURL to append uuid=magic in URL
	// map[Set-Cookie:[x-afb-uuid-5678=2b185cc3-276b-4097-91fa-d607eaf937e6; Path=/api; Max-Age=32000000; ...
	//port := strings.Split(xs.BaseURL, ":")[2]
	//"x-afb-uuid-" + port

	xs.client, err = common.HTTPNewClient(xs.BaseURL,
		common.HTTPClientConfig{
			//HeaderClientKeyName: "Xds-Sid",
			HeaderAPIKeyName: "token",
			Apikey:           "HELLO",
			URLPrefix:        "/api/xds",
			CsrfDisable:      true,
			LogOut:           xs.logOut,
			LogPrefix:        "XDSSUPERV: ",
			LogLevel:         common.HTTPLogLevelWarning,
		})

	xs.client.SetLogLevel(xs.Log.Level.String())

	if err != nil {
		msg := ": " + err.Error()
		if strings.Contains(err.Error(), "connection refused") {
			msg = fmt.Sprintf("(url: %s)", xs.BaseURL)
		}
		return fmt.Errorf("ERROR: cannot connect to XDS Supervisor %s", msg)
	}
	if xs.client == nil {
		return fmt.Errorf("ERROR: cannot connect to XDS Supervisor (null client)")
	}

	return nil
}

// _Connect Established HTTP and WS connection
func (xs *XdsSupervisor) _Connect(reConn bool) error {

	var res interface{}
	if err := xs.client.Get(xs._BuildURL("/ping"), &res); err != nil {
		xs.Connected = false
		if !reConn {
			xs._NotifyState()
		}
		return err
	}

	xs.Connected = true

	// Call OnConnect callback
	if xs.cbOnConnect != nil {
		xs.cbOnConnect(xs)
	}

	xs._NotifyState()
	return nil
}

// _NotifyState Send event to notify changes
func (xs *XdsSupervisor) _NotifyState() {

	/* TODO
	evSts := xaapiv1.ServerCfg{
		ID:         xs.ID,
		URL:        xs.BaseURL,
		APIURL:     xs.APIURL,
		PartialURL: xs.PartialURL,
		ConnRetry:  xs.ConnRetry,
		Connected:  xs.Connected,
	}
	if err := xs.events.Emit(xaapiv1.EVTServerConfig, evSts, ""); err != nil {
		xs.Log.Warningf("Cannot notify XdsServer state change: %v", err)
	}
	*/
}