// Copyright (C) 2014-2018 Goodrain Co., Ltd.
// RAINBOND, Application Management Platform

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package handler

import (
	"crypto/md5"
	crand "crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"math/big"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/sirupsen/logrus"

	api_model "github.com/goodrain/rainbond/api/model"
	"github.com/goodrain/rainbond/api/util"
	"github.com/goodrain/rainbond/cmd/api/option"
	"github.com/goodrain/rainbond/db"
	dbmodel "github.com/goodrain/rainbond/db/model"
)

//CloudAction  cloud action struct
type CloudAction struct {
	RegionTag string
	APISSL    bool
	CAPath    string
	KeyPath   string
}

//CreateCloudManager get cloud manager
func CreateCloudManager(conf option.Config) *CloudAction {
	return &CloudAction{
		APISSL:    conf.APISSL,
		RegionTag: conf.RegionTag,
		CAPath:    conf.APICertFile,
		KeyPath:   conf.APIKeyFile,
	}
}

//TokenDispatcher token
func (c *CloudAction) TokenDispatcher(gt *api_model.GetUserToken) (*api_model.TokenInfo, *util.APIHandleError) {
	//TODO: product token, 启动api时需要添加该参数
	//token包含 eid，数据中心标识，可控范围，有效期
	ti := &api_model.TokenInfo{
		EID: gt.Body.EID,
	}
	token := c.createToken(gt)
	var oldToken string
	tokenInfos, err := db.GetManager().RegionUserInfoDao().GetTokenByEid(gt.Body.EID)
	if err != nil {
		if err.Error() == gorm.ErrRecordNotFound.Error() {
			goto CREATE
		}
		return nil, util.CreateAPIHandleErrorFromDBError("get user token info", err)
	}
	ti.CA = tokenInfos.CA
	//ti.Key = tokenInfos.Key
	ti.Token = token
	oldToken = tokenInfos.Token
	tokenInfos.Token = token
	tokenInfos.ValidityPeriod = gt.Body.ValidityPeriod
	if err := db.GetManager().RegionUserInfoDao().UpdateModel(tokenInfos); err != nil {
		return nil, util.CreateAPIHandleErrorFromDBError("recreate region user info", err)
	}
	tokenInfos.CA = ""
	tokenInfos.Key = ""
	GetTokenIdenHandler().DeleteTokenFromMap(oldToken, tokenInfos)
	return ti, nil
CREATE:
	ti.Token = token
	logrus.Debugf("create token %v", token)
	rui := &dbmodel.RegionUserInfo{
		EID:            gt.Body.EID,
		RegionTag:      c.RegionTag,
		APIRange:       gt.Body.Range,
		ValidityPeriod: gt.Body.ValidityPeriod,
		Token:          token,
	}
	if c.APISSL {
		ca, key, err := c.CertDispatcher(gt)
		if err != nil {
			return nil, util.CreateAPIHandleError(500, fmt.Errorf("create ca or key error"))
		}
		rui.CA = string(ca)
		rui.Key = string(key)
		ti.CA = string(ca)
		//ti.Key = string(key)
	}
	if gt.Body.Range == "" {
		rui.APIRange = dbmodel.SERVERSOURCE
	}
	GetTokenIdenHandler().AddTokenIntoMap(rui)
	if err := db.GetManager().RegionUserInfoDao().AddModel(rui); err != nil {
		return nil, util.CreateAPIHandleErrorFromDBError("create region user info", err)
	}
	return ti, nil
}

//GetTokenInfo GetTokenInfo
func (c *CloudAction) GetTokenInfo(eid string) (*dbmodel.RegionUserInfo, *util.APIHandleError) {
	tokenInfos, err := db.GetManager().RegionUserInfoDao().GetTokenByEid(eid)
	if err != nil {
		return nil, util.CreateAPIHandleErrorFromDBError("get user token info", err)
	}
	return tokenInfos, nil
}

//UpdateTokenTime UpdateTokenTime
func (c *CloudAction) UpdateTokenTime(eid string, vd int) *util.APIHandleError {
	tokenInfos, err := db.GetManager().RegionUserInfoDao().GetTokenByEid(eid)
	if err != nil {
		return util.CreateAPIHandleErrorFromDBError("get user token info", err)
	}
	tokenInfos.ValidityPeriod = vd
	err = db.GetManager().RegionUserInfoDao().UpdateModel(tokenInfos)
	if err != nil {
		return util.CreateAPIHandleErrorFromDBError("update user token info", err)
	}
	return nil
}

//CertDispatcher Cert
func (c *CloudAction) CertDispatcher(gt *api_model.GetUserToken) ([]byte, []byte, error) {
	cert, err := analystCaKey(c.CAPath, "ca")
	if err != nil {
		return nil, nil, err
	}
	//解析私钥
	keyFile, err := analystCaKey(c.KeyPath, "key")
	if err != nil {
		return nil, nil, err
	}
	//keyFile = keyFile.(rsa.PrivateKey)

	validHourTime := (gt.Body.ValidityPeriod - gt.Body.BeforeTime)
	cer := &x509.Certificate{
		SerialNumber: big.NewInt(1), //证书序列号
		Subject: pkix.Name{
			CommonName: fmt.Sprintf("%s@%d", gt.Body.EID, time.Now().Unix()),
			Locality:   []string{fmt.Sprintf("%s", c.RegionTag)},
		},
		NotBefore:             time.Now(),                                                                 //证书有效期开始时间
		NotAfter:              time.Now().Add(time.Second * time.Duration(validHourTime)),                 //证书有效期结束时间
		BasicConstraintsValid: true,                                                                       //基本的有效性约束
		IsCA:                  false,                                                                      //是否是根证书
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, //证书用途(客户端认证，数据加密)
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
		//EmailAddresses: []string{"region@test.com"},
		//IPAddresses:    []net.IP{net.ParseIP("192.168.1.59")},
	}
	priKey, err := rsa.GenerateKey(crand.Reader, 2048)
	if err != nil {
		return nil, nil, err
	}
	ca, err := x509.CreateCertificate(crand.Reader, cer, cert.(*x509.Certificate), &priKey.PublicKey, keyFile)
	if err != nil {
		return nil, nil, err
	}

	//编码证书文件和私钥文件
	caPem := &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: ca,
	}
	ca = pem.EncodeToMemory(caPem)

	buf := x509.MarshalPKCS1PrivateKey(priKey)
	keyPem := &pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: buf,
	}
	key := pem.EncodeToMemory(keyPem)
	return ca, key, nil
}

func analystCaKey(path, kind string) (interface{}, error) {
	fileInfo, err := ioutil.ReadFile(path)
	if err != nil {
		return "", nil
	}
	fileBlock, _ := pem.Decode(fileInfo)
	switch kind {
	case "ca":
		cert, err := x509.ParseCertificate(fileBlock.Bytes)
		if err != nil {
			return "", nil
		}
		return cert, nil
	case "key":
		praKey, err := x509.ParsePKCS1PrivateKey(fileBlock.Bytes)
		if err != nil {
			return "", nil
		}
		return praKey, nil
	}
	return "", nil
}

func (c *CloudAction) createToken(gt *api_model.GetUserToken) string {
	fullStr := fmt.Sprintf("%s-%s-%s-%d-%d", gt.Body.EID, c.RegionTag, gt.Body.Range, gt.Body.ValidityPeriod, int(time.Now().Unix()))
	h := md5.New()
	h.Write([]byte(fullStr))
	return hex.EncodeToString(h.Sum(nil))
}
