package main

import (
	"context"
	"encoding/hex"
	"errors"
	"math/rand"
	"net/http"
	"sparrow/pkg/models"
	"sparrow/pkg/rpcs"
	"sparrow/pkg/server"
	"sparrow/pkg/token"
	"strings"

	"github.com/opentracing/opentracing-go/ext"

	"github.com/opentracing/opentracing-go"

	"github.com/martini-contrib/render"
)

const (
	ErrOK                  = 0
	ErrSystemFault         = 10001
	ErrDeviceNotFound      = 10002
	ErrWrongSecret         = 10003
	ErrProtocolNotSuported = 10004
)

func renderError(code int, err error) Common {
	result := Common{}
	result.Code = code
	result.Message = err.Error()
	server.Log.Error(err.Error())
	return result
}

// DeviceRegisterArgs device register args
type DeviceRegisterArgs struct {
	ProductKey string `json:"product_key"  binding:"required"`
	DeviceCode string `json:"device_code"  binding:"required"`
	Version    string `json:"version"  binding:"required"`
	ModuleName string `json:"module" binding:"required"`
}

// DeviceAuthArgs device authentication args
type DeviceAuthArgs struct {
	DeviceId     int64  `json:"device_id" binding:"required"`
	DeviceSecret string `json:"device_secret" binding:"required"`
	Protocol     string `json:"protocol" binding:"required"`
}

// RegisterDevice 设备激活
func RegisterDevice(args DeviceRegisterArgs, r render.Render) {
	server.Log.Printf("ACTION RegisterDevice, args:: %v ", args)

	rpcargs := &rpcs.ArgsDeviceRegister{
		ProductKey:    args.ProductKey,
		DeviceCode:    args.DeviceCode,
		DeviceVersion: args.Version,
		ModuleName:    args.ModuleName,
	}
	device := &models.Device{}
	err := server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.RegisterDevice", rpcargs, device)
	if err != nil {
		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
		return
	}
	server.Log.Infof("register device success: %v", device)

	result := DeviceRegisterResponse{}
	result.Data = DeviceRegisterData{
		DeviceId:         int64(device.ID),
		DeviceSecret:     device.DeviceSecret,
		DeviceKey:        device.DeviceKey,
		DeviceIdentifier: device.DeviceIdentifier,
	}
	r.JSON(http.StatusOK, result)
	return
}

// AuthDevice device auth
func AuthDevice(args DeviceAuthArgs, r render.Render) {
	server.Log.Printf("ACTION AuthDevice, args:: %v", args)
	device := &models.Device{}

	span, ctx := opentracing.StartSpanFromContext(context.Background(), "AuthDevice")
	defer span.Finish()

	ext.SpanKindRPCClient.Set(span)
	arg := uint64(args.DeviceId)
	err := server.RPCCallByName(ctx, rpcs.RegistryServerName, "Registry.FindDeviceById", &arg, device)
	if err != nil {
		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
		return
	}

	if device.DeviceSecret != args.DeviceSecret {
		// device secret is wrong.
		r.JSON(http.StatusOK, renderError(ErrWrongSecret, errors.New("wrong device secret.")))
		return
	}

	hepler := token.NewHelper(*confRedisHost, *confRedisPort, *confRedisDb)
	token, err := hepler.GenerateToken(device.RecordId)
	if err != nil {
		r.JSON(http.StatusOK, renderError(ErrSystemFault, err))
		return
	}

	var hosts []string
	switch args.Protocol {
	case "http":
		hosts, err = server.GetServerHosts(strings.ToUpper(args.Protocol)+"Access", "httphost")
	case "mqtt":
		hosts, err = server.GetServerHosts(strings.ToUpper(args.Protocol)+"Access", "tcphost")
	case "coap":
		hosts, err = server.GetServerHosts(strings.ToUpper(args.Protocol)+"Access", "udphost")
	default:
		err = errors.New("unsuported protocol: " + args.Protocol)
	}
	if err != nil {
		r.JSON(http.StatusOK, renderError(ErrProtocolNotSuported, err))
		return
	}

	// just get a random host
	host := hosts[rand.Intn(len(hosts))]

	result := DeviceAuthResponse{}
	result.Data = DeviceAuthData{
		AccessToken: hex.EncodeToString(token),
		AccessAddr:  host,
	}

	server.Log.Infof("auth device success: %v, token: %x, access: %v", device, token, host)

	r.JSON(http.StatusOK, result)
	return
}
