package main

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

	"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"`
}

type AccessAuthArgs struct {
	UserName string `json:"username"`
	Password string `json:"password"`
}

type AccessAuthResp struct {
	Result      string `json:"result"`       // 可选 "allow" | "deny" | "ignore"
	IsSuperuser bool   `json:"is_superuser"` // false
}

// 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
}

// DeviceAccessAuth emqx 设备接入认证
func DeviceAccessAuth(args AccessAuthArgs, r render.Render) {
	server.Log.Printf("ACTION DeviceAccessAuth, args:: %v", args)

	result := AccessAuthResp{}
	if (args.UserName == "sparrow_test" && args.Password == "sparrow_test") || args.Password == "z6Kq9F3LbT" {
		result.Result = "allow"
		r.JSON(http.StatusOK, result)
		return
	}
	deviceId, err := ClientIDToDeviceID(args.UserName)
	if err != nil {
		server.Log.Errorf("invalid Identify: %s", args.UserName)
		result.Result = "deny"
		r.JSON(http.StatusOK, result)
		return
	}
	device := &models.Device{}
	err = server.RPCCallByName(nil, rpcs.RegistryServerName, "Registry.FindDeviceById", deviceId, device)
	if err != nil {
		server.Log.Errorf("device not found %d", deviceId)
		result.Result = "deny"
		r.JSON(http.StatusOK, result)
		return
	}
	// parse token
	token, err := hex.DecodeString(args.Password)
	if err != nil {
		server.Log.Errorf("token format error : %v", err)
		result.Result = "deny"
		r.JSON(http.StatusOK, result)
		return
	}
	// validate token
	if err := validateToken(device.RecordId, token); err != nil {
		server.Log.Errorf("validate token error : %v", err)
		result.Result = "deny"
		r.JSON(http.StatusOK, result)
		return
	}
	result.Result = "allow"
	r.JSON(http.StatusOK, result)
}

func validateToken(deviceRecordId string, token []byte) error {
	args := rpcs.ArgsValidateDeviceAccessToken{
		Id:          deviceRecordId,
		AccessToken: token,
	}
	reply := rpcs.ReplyValidateDeviceAccessToken{}
	err := server.RPCCallByName(nil, rpcs.DeviceManagerName, "DeviceManager.ValidateDeviceAccessToken", args, &reply)
	if err != nil {
		return err
	}
	return nil
}

// AuthDevice device auth
func AuthDevice(args DeviceAuthArgs, r render.Render) {
	device := &models.Device{}

	arg := uint64(args.DeviceId)
	err := server.RPCCallByName(context.Background(), rpcs.RegistryServerName, "Registry.FindDeviceById", &arg, device)
	if err != nil {
		r.JSON(http.StatusOK, renderError(ErrDeviceNotFound, err))
		return
	}
	fmt.Printf(fmt.Sprintf("%s, %s", device.DeviceSecret, args.DeviceSecret))
	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 strings.ToLower(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")
	case "mqttx": // TODO: 增加配置文件支持
		hosts = []string{
			"114.115.251.196:1883",
		}
	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
}

func ClientIDToDeviceID(identify string) (uint64, error) {
	deviceId, err := strconv.ParseUint(identify, 16, 64)
	if err != nil {
		return uint64(0), err
	}

	return deviceId, nil
}
