/*
 * Copyright 2018 Intel Corporation, Inc
 *
 * 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 db

import (
	"context"
	"time"

	pkgerrors "github.com/pkg/errors"
	"go.etcd.io/etcd/clientv3"
	"go.etcd.io/etcd/pkg/transport"
)

// EtcdConfig Configuration values needed for Etcd Client
type EtcdConfig struct {
	Endpoint string
	CertFile string
	KeyFile  string
	CAFile   string
}

// EtcdStore Interface needed for mocking
type EtcdStore interface {
	Get(key string) ([]byte, error)
	GetAll(key string) ([][]byte, error)
	Put(key, value string) error
	Delete(key string) error
	DeletePrefix(keyPrefix string) error
}

// EtcdClient for Etcd
type EtcdClient struct {
	cli *clientv3.Client
}

// Etcd handle for interface
var Etcd EtcdStore

// NewEtcdClient function initializes Etcd client
func NewEtcdClient(store *clientv3.Client, c EtcdConfig) error {
	var err error
	Etcd, err = newClient(store, c)
	return err
}

func newClient(store *clientv3.Client, c EtcdConfig) (EtcdClient, error) {
	if store == nil {
		tlsInfo := transport.TLSInfo{
			CertFile: c.CertFile,
			KeyFile:  c.KeyFile,
			CAFile:   c.CAFile,
		}
		tlsConfig, err := tlsInfo.ClientConfig()
		if err != nil {
			return EtcdClient{}, pkgerrors.Errorf("Error creating etcd TLSInfo: %s", err.Error())
		}
		// NOTE: Client relies on nil tlsConfig
		// for non-secure connections, update the implicit variable
		if len(c.CertFile) == 0 && len(c.KeyFile) == 0 && len(c.CAFile) == 0 {
			tlsConfig = nil
		}
		endpoint := ""
		if tlsConfig == nil {
			endpoint = "http://" + c.Endpoint + ":2379"
		} else {
			endpoint = "https://" + c.Endpoint + ":2379"
		}

		store, err = clientv3.New(clientv3.Config{
			Endpoints:   []string{endpoint},
			DialTimeout: 5 * time.Second,
			TLS:         tlsConfig,
		})
		if err != nil {
			return EtcdClient{}, pkgerrors.Errorf("Error creating etcd client: %s", err.Error())
		}
	}

	return EtcdClient{
		cli: store,
	}, nil
}

// Put values in Etcd DB
func (e EtcdClient) Put(key, value string) error {

	if e.cli == nil {
		return pkgerrors.Errorf("Etcd Client not initialized")
	}
	_, err := e.cli.Put(context.Background(), key, value)
	if err != nil {
		return pkgerrors.Errorf("Error creating etcd entry: %s", err.Error())
	}
	return nil
}

// Get values from Etcd DB
func (e EtcdClient) Get(key string) ([]byte, error) {

	if e.cli == nil {
		return nil, pkgerrors.Errorf("Etcd Client not initialized")
	}
	getResp, err := e.cli.Get(context.Background(), key)
	if err != nil {
		return nil, pkgerrors.Errorf("Error getting etcd entry: %s", err.Error())
	}
	if getResp.Count == 0 {
		return nil, pkgerrors.Errorf("Key doesn't exist")
	}
	return getResp.Kvs[0].Value, nil
}

// GetAll sub values from Etcd DB
func (e EtcdClient) GetAll(key string) ([][]byte, error) {
	if e.cli == nil {
		return nil, pkgerrors.Errorf("Etcd Client not initialized")
	}
	getResp, err := e.cli.Get(context.Background(), key, clientv3.WithPrefix())
	if err != nil {
		return nil, pkgerrors.Errorf("Error getting etcd entry: %s", err.Error())
	}
	result := make([][]byte, 0)
	for _, v := range getResp.Kvs {
		result = append(result, v.Value)
	}
	return result, nil
}

// Delete values from Etcd DB
func (e EtcdClient) Delete(key string) error {

	if e.cli == nil {
		return pkgerrors.Errorf("Etcd Client not initialized")
	}
	_, err := e.cli.Delete(context.Background(), key)
	if err != nil {
		return pkgerrors.Errorf("Delete failed etcd entry:%s", err.Error())
	}
	return nil
}

// Delete values by prefix from Etcd DB
func (e EtcdClient) DeletePrefix(keyPrefix string) error {

	if e.cli == nil {
		return pkgerrors.Errorf("Etcd Client not initialized")
	}
	_, err := e.cli.Delete(context.Background(), keyPrefix, clientv3.WithPrefix())
	if err != nil {
		return pkgerrors.Errorf("Delete prefix failed etcd entry:%s", err.Error())
	}
	return nil
}