package queries

import (
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	s "github.com/fiskerinc/cloud-services/pkg/security"
	"github.com/go-pg/pg/v10"
	"github.com/go-pg/pg/v10/orm"
	"github.com/pkg/errors"
)

const sqlLastManifestEnv = "WITH sub AS (SELECT COALESCE((SELECT env FROM update_manifests WHERE id IN (SELECT update_manifest_id FROM car_updates WHERE id IN (SELECT MAX(id) FROM car_updates WHERE vin = ?))), 'current') as env)"
const sqlCarUpdateIDEnv = "WITH sub AS (SELECT COALESCE((SELECT env FROM update_manifests WHERE update_manifests.id IN (SELECT update_manifest_id FROM car_updates WHERE id = ?)), 'current') as env)"
const sqlECCPrivKeysEnv = " SELECT priv_key_level_1, priv_key_level_2, priv_key_level_3, ecu, ecc_keys.env FROM ecc_keys INNER JOIN sub ON ecc_keys.env = sub.env ORDER BY ecu;"

type EccKeysInterface interface {
	Insert(keys common.ECCKeys) (orm.Result, error)
	SelectAllPrivateKeys() ([]common.ECCKeys, error)
	SelectAllPrivateKeysByEnv(env string) ([]common.ECCKeys, error)
	SelectPublicKeysByECUByEnv(ecu string, env string) (common.ECCKeys, error)
	SelectAllPublicKeysByEnv(env string) ([]common.ECCKeys, error)
	SelectPrivateKeysByECUsEnv(ecus []string, env string) ([]common.ECCKeys, error)
	SelectAllPrivateKeysByVIN(vin string) ([]common.ECCKeys, error)
	SelectAllPrivateKeysByCarUpdateID(id int64) ([]common.ECCKeys, error)
}

type EccKeys struct {
	QueryBase
}

func (ek *EccKeys) Insert(keys common.ECCKeys) (orm.Result, error) {
	enc := s.Encrypt{}
	encryptor, err := enc.GetEncryptor()
	if err != nil {
		return nil, err
	}

	keys.PubKey1.SetBytes(encryptor.EncryptChunk(keys.PubKey1.Bytes()))
	keys.PrivKey1.SetBytes(encryptor.EncryptChunk(keys.PrivKey1.Bytes()))
	keys.PubKey2.SetBytes(encryptor.EncryptChunk(keys.PubKey2.Bytes()))
	keys.PrivKey2.SetBytes(encryptor.EncryptChunk(keys.PrivKey2.Bytes()))
	keys.PubKey3.SetBytes(encryptor.EncryptChunk(keys.PubKey3.Bytes()))
	keys.PrivKey3.SetBytes(encryptor.EncryptChunk(keys.PrivKey3.Bytes()))

	return ek.resultWithStack(ek.GetDBConn().Model(&keys).Insert())
}

// Selects all private keys and ECU's
func (ek *EccKeys) SelectAllPrivateKeys() ([]common.ECCKeys, error) {
	return ek.selectKeys("current", "priv_key_level_1", "priv_key_level_2", "priv_key_level_3", "ecu")
}

// Selects all private keys and ECU's by env
func (ek *EccKeys) SelectAllPrivateKeysByEnv(env string) ([]common.ECCKeys, error) {
	return ek.selectKeys(env, "priv_key_level_1", "priv_key_level_2", "priv_key_level_3", "ecu")
}

// Selects public keys associated with ECU by env
func (ek *EccKeys) SelectAllPublicKeysByEnv(env string) ([]common.ECCKeys, error) {
	return ek.selectKeys(env, "pub_key_level_1", "pub_key_level_2", "pub_key_level_3", "ecu")
}

// Selects keys associated with ECU
func (ek *EccKeys) SelectAllKeys() ([]common.ECCKeys, error) {
	return ek.selectKeys("current", "pub_key_level_1", "pub_key_level_2", "pub_key_level_3", "priv_key_level_1", "priv_key_level_2", "priv_key_level_3", "ecu")
}

// Selects public keys associated with ECU by env
func (ek *EccKeys) SelectPublicKeysByECUByEnv(ecu string, env string) (common.ECCKeys, error) {
	ecckey := common.ECCKeys{}

	err := ek.GetDBConn().Model(&ecckey).Column("pub_key_level_1", "pub_key_level_2", "pub_key_level_3", "ecu").Where("ecu = ? AND env = ?", ecu, env).Select()
	if err != nil {
		return ecckey, errors.WithStack(err)
	}

	err = ek.decrypt(&ecckey)

	return ecckey, err
}

func (ek *EccKeys) SelectPrivateKeysByECUsEnv(ecus []string, env string) ([]common.ECCKeys, error) {
	var keys []common.ECCKeys

	if env == "" {
		env = common.EnvCurrent
	}

	err := ek.GetDBConn().Model(&keys).
		Column("priv_key_level_1", "priv_key_level_2", "priv_key_level_3", "ecu").
		Where("ecu IN (?) AND env = ?", pg.In(ecus), env).
		Order("ecu").
		Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	for i := range keys {
		err = ek.decrypt(&keys[i])
		if err != nil {
			return nil, err
		}
	}

	return keys, nil
}

// SelectAllPrivateKeysByVIN returns the ECC keys for an environment based on the last update sent to the VIN
// If no update was sent, it will send the keys for the current environment
func (ek *EccKeys) SelectAllPrivateKeysByVIN(vin string) ([]common.ECCKeys, error) {
	var keys []common.ECCKeys

	_, err := ek.GetDBConn().Query(&keys, sqlLastManifestEnv+sqlECCPrivKeysEnv, vin)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if len(keys) == 0 {
		return nil, errors.Errorf("No ECC keys for %s", vin)
	}

	logger.Info().Msgf("SelectAllPrivateKeysByVIN %s %s", vin, keys[0].Env)

	for i := range keys {
		keys[i].Env = ""
		err = ek.decrypt(&keys[i])
		if err != nil {
			return nil, err
		}
	}

	return keys, nil
}

func (ek *EccKeys) SelectAllPrivateKeysByCarUpdateID(id int64) ([]common.ECCKeys, error) {
	var keys []common.ECCKeys

	_, err := ek.GetDBConn().Query(&keys, sqlCarUpdateIDEnv+sqlECCPrivKeysEnv, id)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if len(keys) == 0 {
		return nil, errors.Errorf("No ECC keys for car update id %d", id)
	}

	logger.Info().Msgf("SelectAllPrivateKeysByCarUpdateID %d %s", id, keys[0].Env)

	for i := range keys {
		keys[i].Env = ""
		err = ek.decrypt(&keys[i])
		if err != nil {
			return nil, err
		}
	}

	return keys, nil
}

func (ek *EccKeys) selectKeys(env string, columns ...string) ([]common.ECCKeys, error) {
	ecckeys := []common.ECCKeys{}

	err := ek.GetDBConn().Model(&ecckeys).Column(columns...).Where("env = ?", env).Order("ecu").Select()
	if err != nil {
		return ecckeys, errors.WithStack(err)
	}

	for i := range ecckeys {
		err = ek.decrypt(&ecckeys[i])
	}

	return ecckeys, err
}

func (ek *EccKeys) decrypt(eccKeys *common.ECCKeys) error {
	enc := s.Encrypt{}
	encryptor, err := enc.GetEncryptor()
	if err != nil {
		return err
	}

	err = ek.decryptKey(encryptor, eccKeys.PrivKey1)
	if err != nil {
		return err
	}

	err = ek.decryptKey(encryptor, eccKeys.PrivKey2)
	if err != nil {
		return err
	}

	err = ek.decryptKey(encryptor, eccKeys.PrivKey3)
	if err != nil {
		return err
	}

	err = ek.decryptKey(encryptor, eccKeys.PubKey1)
	if err != nil {
		return err
	}

	err = ek.decryptKey(encryptor, eccKeys.PubKey2)
	if err != nil {
		return err
	}
	err = ek.decryptKey(encryptor, eccKeys.PubKey3)
	if err != nil {
		return err
	}

	return nil
}

func (ek *EccKeys) decryptKey(encryptor s.IEncryptor, key *common.BinaryHex) error {
	if key != nil {
		keys, err := encryptor.DecryptChunk(key.Bytes())
		if err != nil {
			return err
		}
		key.SetBytes(keys)
	}
	return nil
}
