/*
 * Copyright (c) 2022  The AdvantEDGE Authors
 *
 * 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.
 *
 * MEEP Demo 4 API
 * Demo 4 is an edge application that can be used with AdvantEDGE or ETSI MEC Sandbox to demonstrate MEC016 usage
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * API version: 0.0.1
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */
package server

import (
	"context"
	"errors"
	//"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	asc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-app-support-client"
	tm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-dai-mgr"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
	sbx "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sandbox-ctrl-client"
	smc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-service-mgmt-client"

	"github.com/google/uuid"
)

const (
	amName      = "pc"
	amNamespace = "postgis-ns"
	amDBUser    = "postgres"
	amDBPwd     = "pwd"
	amDBHost    = ""
	amDBPort    = ""
)

const ( // FIXME confoguration file required
	environment = "advantedge"
	sandboxUrl  = "yannsb"
	mep         = "mep1"
	localUrl    = "mec-platform.etsi.org"
	localPort   = ":31124"
	mecUrl      = "sandbox-mec.etsi.org"
	appNameBase = "onboarded-demo4"
)

// Sandbox controller client
var sandBoxClient *sbx.APIClient
var sbxCtrlUrl string = "http://meep-sandbox-ctrl"
var instanceName string = uuid.New().String()
var appName string = appNameBase + "-" + instanceName[0:32]

// App-enablement client
var srvMgmtClient *smc.APIClient
var srvMgmtClientPath string
var appSupportClient *asc.APIClient
var appSupportClientPath string

var appTerminationSent bool
var terminationSubscriptionId string
var terminated bool
var terminateNotification bool
var appEnabled bool
var subscriptions ApplicationInstanceSubscriptions
var mutex sync.Mutex

// TODO Models
// List of ams subscription unique to device and their id
type ApplicationInstanceSubscriptions struct {
	AppTerminationSubscription *ApplicationInstanceAppTerminationSubscription `json:"AppTerminationSubscription,omitempty"`
}

// This type represents the information that the MEC platform notifies the subscribed application instance about  the corresponding application instance termination/stop.
type ApplicationInstanceAppTerminationSubscription struct {
	SubId string `json:"subId,omitempty"`
}

// DaiMgr
var daiMgr *tm.DaiMgr = nil

func Init() (err error) {
	// Setup application support client & service management client
	appSupportClientCfg := asc.NewConfiguration()
	srvMgmtClientCfg := smc.NewConfiguration()
	if environment == "advantedge" {
		appSupportClientCfg.BasePath = "http://" + localUrl + "/" + sandboxUrl + "/mec_app_support/v1"
		srvMgmtClientCfg.BasePath = "http://" + localUrl + "/" + sandboxUrl + "/mec_service_mgmt/v1"
	} else {
		appSupportClientCfg.BasePath = mecUrl + "/mec_app_support/v1"
		srvMgmtClientCfg.BasePath = mecUrl + "/mec_service_mgmt/v1"
	}
	log.Info("Init: appSupportClientCfg: ", appSupportClientCfg)

	// Register UE application into sandbox controller
	sandBoxClientCfg := sbx.NewConfiguration()
	sandBoxClientCfg.BasePath = sbxCtrlUrl + "/sandbox-ctrl/v1"
	log.Info("Init: sandBoxClientCfg= ", sandBoxClientCfg)
	sandBoxClient = sbx.NewAPIClient(sandBoxClientCfg)
	var appInfo = sbx.ApplicationInfo{instanceName, appName, mep, "USER", false}
	appInfo, err = setApplicationInfo(appInfo)
	if err != nil {
		log.Fatal("Failed to set UE application resource ", appInfo)
	}

	// Create app enablement client
	appSupportClient = asc.NewAPIClient(appSupportClientCfg)
	appSupportClientPath = appSupportClientCfg.BasePath
	if appSupportClient == nil {
		err := errors.New("Failed to create App Enablement App Support REST API client")
		log.Error(err.Error())
		return err
	}
	log.Info("appSupportClient: ", appSupportClient, "/appSupportClientPath: ", appSupportClientPath)

	// Create service management client
	srvMgmtClient = smc.NewAPIClient(srvMgmtClientCfg)
	srvMgmtClientPath = srvMgmtClientCfg.BasePath
	if srvMgmtClient == nil {
		err := errors.New("Failed to create App Enablement Service Management REST API client")
		log.Error(err.Error())
		return err
	}
	log.Info("srvMgmtClient: ", srvMgmtClient, "/srvMgmtClientPath: ", srvMgmtClientPath)

	// Create DAI client
	cfgDai := tm.DaiCfg{
		Name:                     amName,
		Namespace:                amNamespace,
		User:                     amDBUser,
		Pwd:                      amDBPwd,
		Host:                     amDBHost,
		Port:                     amDBPort,
		NotifyAppContextDeletion: nil,
	}
	daiMgr, err := tm.NewDaiMgr(cfgDai)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Connected to DAI Manager: ", daiMgr)

	// Wait a few seconds to get all components up.
	log.Info("Waiting for Service to start")
	time.Sleep(5 * time.Second)

	return nil
}

func setApplicationInfo(appInfo sbx.ApplicationInfo) (appInfoRes sbx.ApplicationInfo, err error) {
	appInfoRes, resp, err := sandBoxClient.ApplicationsApi.ApplicationsPOST(context.TODO(), appInfo)
	if err != nil {
		log.Error(err.Error())
		return appInfoRes, err
	}
	log.Debug("setApplicationInfo: Receive confirmation acknowlegement ", resp.Status)

	return appInfoRes, nil
}

// Channel sync consume channel listen for app termination
func Run() {
	err := onboardedDemoRegister()
	if err != nil {
		log.Fatal(err.Error())
	}

}

// Terminate by deleting all resources allocated on MEC platform & mec app
func Terminate() {

	// Only invoke graceful termination if not terminated (triggerd by mec platform)

	if !terminated {

		if appEnabled {
			log.Info("De-register " + appName + " Application")
		}

		if appTerminationSent {
			// Delete app subscriptions

			// Delete service

			//  Send Confirm Terminate if received notification
			if terminateNotification {
				confirmTerminate(instanceName)
				terminated = true
			}

			log.Debug("Terminate: instanceName= ", instanceName)
			_, err := sandBoxClient.ApplicationsApi.ApplicationsAppInstanceIdDELETE(context.TODO(), instanceName)
			if err != nil {
				log.Info("Failed to delete UE application resource")
			}

		}
	}
}

// Client request to notify mec platform of mec app
func sendReadyConfirmation(appInstanceId string) error {
	var appReady asc.AppReadyConfirmation
	appReady.Indication = "READY"
	log.Info(appSupportClientPath)
	resp, err := appSupportClient.MecAppSupportApi.ApplicationsConfirmReadyPOST(context.TODO(), appReady, appInstanceId)
	if err != nil {
		log.Error(err.Error())
		//log.Error("Failed to receive confirmation acknowlegement ", resp.Status)
		return err
	}
	log.Debug("Receive confirmation acknowlegement ", resp.Status)

	return nil
}

// Client request to sent confirm terminate
func confirmTerminate(appInstanceId string) {
	operationAction := asc.TERMINATING_OperationActionType
	var terminationBody asc.AppTerminationConfirmation
	terminationBody.OperationAction = &operationAction
	resp, err := appSupportClient.MecAppSupportApi.ApplicationsConfirmTerminationPOST(context.TODO(), terminationBody, appInstanceId)
	status := strconv.Itoa(resp.StatusCode)
	if err != nil {
		log.Error("Failed to send confirm termination ", err)
	} else {
		log.Info("Confirm Terminated")
	}
	log.Debug("confirmTerminate: ", status)
}

func onboardedDemoRegister() (err error) {
	mutex.Lock()
	defer mutex.Unlock()

	// Start app registeration
	if !appEnabled {

		// Send confirm ready
		err := sendReadyConfirmation(instanceName)
		if err != nil {
			// Add to activity log for error indicator
			log.Error(err.Error())
			return err
		}

		// Retrieve mec services
		// Store discovered service name into app info model

		// Subscribe to app termination
		appTerminationReference := "http://" + localUrl + localPort + "/application/termination"
		appTerminationId, err := subscribeAppTermination(instanceName, appTerminationReference)
		if err == nil {
			appTerminationSent = true
		}

		// Store app termination subscription id
		var appSubscription ApplicationInstanceAppTerminationSubscription
		appSubscription.SubId = appTerminationId
		subscriptions.AppTerminationSubscription = &appSubscription

	}

	return nil
}

// Client request to subscribe app-termination notifications
func subscribeAppTermination(appInstanceId string, callBackReference string) (string, error) {
	log.Debug("Sending request to mec platform add app terminate subscription api")
	var appTerminationBody asc.AppTerminationNotificationSubscription
	appTerminationBody.SubscriptionType = "AppTerminationNotificationSubscription"
	appTerminationBody.CallbackReference = callBackReference
	appTerminationBody.AppInstanceId = appInstanceId
	appTerminationResponse, _, err := appSupportClient.MecAppSupportApi.ApplicationsSubscriptionsPOST(context.TODO(), appTerminationBody, appInstanceId)

	if err != nil {
		log.Error("Failed to send termination subscription: ", err)
		return "", err
	}

	hRefLink := appTerminationResponse.Links.Self.Href

	// Find subscription id from response
	idPosition := strings.LastIndex(hRefLink, "/")
	if idPosition == -1 {
		log.Error("Error parsing subscription id for subscription")
	}

	terminationSubscriptionId = hRefLink[idPosition+1:]

	log.Debug("subscribeAppTermination: Subscribe to app-termination notification [201]: ", terminationSubscriptionId)
	return terminationSubscriptionId, nil
}
