package gorm

import (
	"github.com/porter-dev/porter/internal/encryption"
	"github.com/porter-dev/porter/internal/models"
	ints "github.com/porter-dev/porter/internal/models/integrations"
	"github.com/porter-dev/porter/internal/repository"
	"gorm.io/gorm"
)

// HelmRepoRepository uses gorm.DB for querying the database
type HelmRepoRepository struct {
	db  *gorm.DB
	key *[32]byte
}

// NewHelmRepoRepository returns a HelmRepoRepository which uses
// gorm.DB for querying the database
func NewHelmRepoRepository(db *gorm.DB, key *[32]byte) repository.HelmRepoRepository {
	return &HelmRepoRepository{db, key}
}

// CreateHelmRepo creates a new helm repo
func (repo *HelmRepoRepository) CreateHelmRepo(hr *models.HelmRepo) (*models.HelmRepo, error) {
	err := repo.EncryptHelmRepoData(hr, repo.key)
	if err != nil {
		return nil, err
	}

	project := &models.Project{}

	if err := repo.db.Where("id = ?", hr.ProjectID).First(&project).Error; err != nil {
		return nil, err
	}

	assoc := repo.db.Model(&project).Association("HelmRepos")

	if assoc.Error != nil {
		return nil, assoc.Error
	}

	if err := assoc.Append(hr); err != nil {
		return nil, err
	}

	// create a token cache by default
	assoc = repo.db.Model(hr).Association("TokenCache")

	if assoc.Error != nil {
		return nil, assoc.Error
	}

	if err := assoc.Append(&hr.TokenCache); err != nil {
		return nil, err
	}

	err = repo.DecryptHelmRepoData(hr, repo.key)

	if err != nil {
		return nil, err
	}

	return hr, nil
}

// ReadHelmRepo gets a helm repo specified by a unique id
func (repo *HelmRepoRepository) ReadHelmRepo(projectID, hrID uint) (*models.HelmRepo, error) {
	hr := &models.HelmRepo{}

	if err := repo.db.Preload("TokenCache").Where("project_id = ? AND id = ?", projectID, hrID).First(&hr).Error; err != nil {
		return nil, err
	}

	repo.DecryptHelmRepoData(hr, repo.key)

	return hr, nil
}

// ListHelmReposByProjectID finds all helm repos
// for a given project id
func (repo *HelmRepoRepository) ListHelmReposByProjectID(
	projectID uint,
) ([]*models.HelmRepo, error) {
	hrs := []*models.HelmRepo{}

	if err := repo.db.Preload("TokenCache").Where("project_id = ?", projectID).Find(&hrs).Error; err != nil {
		return nil, err
	}

	for _, hr := range hrs {
		repo.DecryptHelmRepoData(hr, repo.key)
	}

	return hrs, nil
}

// UpdateHelmRepo modifies an existing HelmRepo in the database
func (repo *HelmRepoRepository) UpdateHelmRepo(
	hr *models.HelmRepo,
) (*models.HelmRepo, error) {
	if err := repo.db.Save(hr).Error; err != nil {
		return nil, err
	}

	return hr, nil
}

// UpdateHelmRepoTokenCache updates the helm repo for a registry
func (repo *HelmRepoRepository) UpdateHelmRepoTokenCache(
	tokenCache *ints.HelmRepoTokenCache,
) (*models.HelmRepo, error) {
	if tok := tokenCache.Token; len(tok) > 0 {
		cipherData, err := encryption.Encrypt(tok, repo.key)
		if err != nil {
			return nil, err
		}

		tokenCache.Token = cipherData
	}

	hr := &models.HelmRepo{}

	if err := repo.db.Where("id = ?", tokenCache.HelmRepoID).First(&hr).Error; err != nil {
		return nil, err
	}

	hr.TokenCache.Token = tokenCache.Token
	hr.TokenCache.Expiry = tokenCache.Expiry

	if err := repo.db.Save(hr).Error; err != nil {
		return nil, err
	}

	return hr, nil
}

// DeleteHelmRepo removes a registry from the db
func (repo *HelmRepoRepository) DeleteHelmRepo(
	hr *models.HelmRepo,
) error {
	// clear TokenCache association
	assoc := repo.db.Model(hr).Association("TokenCache")

	if assoc.Error != nil {
		return assoc.Error
	}

	if err := assoc.Clear(); err != nil {
		return err
	}

	if err := repo.db.Where("id = ?", hr.ID).Delete(&models.HelmRepo{}).Error; err != nil {
		return err
	}

	return nil
}

// EncryptHelmRepoData will encrypt the user's helm repo data before writing
// to the DB
func (repo *HelmRepoRepository) EncryptHelmRepoData(
	hr *models.HelmRepo,
	key *[32]byte,
) error {
	if tok := hr.TokenCache.Token; len(tok) > 0 {
		cipherData, err := encryption.Encrypt(tok, key)
		if err != nil {
			return err
		}

		hr.TokenCache.Token = cipherData
	}

	return nil
}

// DecryptHelmRepoData will decrypt the user's helm repo data before returning it
// from the DB
func (repo *HelmRepoRepository) DecryptHelmRepoData(
	hr *models.HelmRepo,
	key *[32]byte,
) error {
	if tok := hr.TokenCache.Token; len(tok) > 0 {
		plaintext, err := encryption.Decrypt(tok, key)
		if err != nil {
			return err
		}

		hr.TokenCache.Token = plaintext
	}

	return nil
}
