// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

package vault

import (
	"context"
	"fmt"

	"github.com/hashicorp/boundary/globals"
	"github.com/hashicorp/boundary/internal/credential"
	"github.com/hashicorp/boundary/internal/credential/vault/store"
	"github.com/hashicorp/boundary/internal/db/timestamp"
	"github.com/hashicorp/boundary/internal/errors"
	"github.com/hashicorp/boundary/internal/oplog"
	"github.com/hashicorp/boundary/internal/types/resource"
	"google.golang.org/protobuf/proto"
)

// A Method represents an HTTP method used for communicating with Vault.
type Method string

// HTTP methods use for communicating with Vault.
const (
	MethodGet  Method = "GET"
	MethodPost Method = "POST"
)

// A CredentialLibrary contains a Vault path and is owned by a credential
// store.
type CredentialLibrary struct {
	*store.CredentialLibrary
	tableName string `gorm:"-"`

	MappingOverride MappingOverride `gorm:"-"`
}

// NewCredentialLibrary creates a new in memory CredentialLibrary
// for a Vault backend at vaultPath assigned to storeId.
// Name, description, method, request body, credential type, and mapping
// override are the only valid options. All other options are ignored.
func NewCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*CredentialLibrary, error) {
	const op = "vault.NewCredentialLibrary"
	opts := getOpts(opt...)

	l := &CredentialLibrary{
		MappingOverride: opts.withMappingOverride,
		CredentialLibrary: &store.CredentialLibrary{
			StoreId:         storeId,
			Name:            opts.withName,
			Description:     opts.withDescription,
			VaultPath:       vaultPath,
			HttpRequestBody: opts.withRequestBody,
			HttpMethod:      string(opts.withMethod),
			CredentialType:  string(opts.withCredentialType),
		},
	}

	return l, nil
}

func (l *CredentialLibrary) validate(ctx context.Context, caller errors.Op) error {
	switch {
	case !validMappingOverride(l.MappingOverride, l.CredentialType()):
		return errors.New(ctx, errors.VaultInvalidMappingOverride, caller, "invalid credential type for mapping override")
	}
	return nil
}

func allocCredentialLibrary() *CredentialLibrary {
	return &CredentialLibrary{
		CredentialLibrary: &store.CredentialLibrary{},
	}
}

func (l *CredentialLibrary) clone() *CredentialLibrary {
	var m MappingOverride
	if l.MappingOverride != nil {
		m = l.MappingOverride.clone()
	}

	cp := proto.Clone(l.CredentialLibrary)
	return &CredentialLibrary{
		MappingOverride:   m,
		CredentialLibrary: cp.(*store.CredentialLibrary),
	}
}

func (l *CredentialLibrary) setId(i string) {
	l.PublicId = i
	if l.MappingOverride != nil {
		l.MappingOverride.setLibraryId(i)
	}
}

// TableName returns the table name.
func (l *CredentialLibrary) TableName() string {
	if l.tableName != "" {
		return l.tableName
	}
	return "credential_vault_generic_library"
}

// SetTableName sets the table name.
func (l *CredentialLibrary) SetTableName(n string) {
	l.tableName = n
}

// GetResourceType returns the resource type of the CredentialLibrary
func (l *CredentialLibrary) GetResourceType() resource.Type {
	return resource.CredentialLibrary
}

func (l *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata {
	metadata := oplog.Metadata{
		"resource-public-id": []string{l.PublicId},
		"resource-type":      []string{"credential-vault-library"},
		"op-type":            []string{op.String()},
	}
	if l.StoreId != "" {
		metadata["store-id"] = []string{l.StoreId}
	}
	return metadata
}

// CredentialType returns the type of credential the library retrieves.
func (l *CredentialLibrary) CredentialType() globals.CredentialType {
	switch ct := l.GetCredentialType(); ct {
	case "":
		return globals.UnspecifiedCredentialType
	default:
		return globals.CredentialType(ct)
	}
}

var _ credential.Library = (*CredentialLibrary)(nil)

// listCredentialLibraryResult represents the result of the
// list queries used to list all credential libraries.
type listCredentialLibraryResult struct {
	PublicId                  string
	StoreId                   string
	ProjectId                 string
	Name                      string
	Description               string
	VaultPath                 string
	CredentialType            string
	HttpMethod                string
	HttpRequestBody           string
	Username                  string
	KeyType                   string
	Ttl                       string
	KeyId                     string
	CriticalOptions           string
	Extensions                string
	AdditionalValidPrincipals string
	CreateTime                *timestamp.Timestamp
	UpdateTime                *timestamp.Timestamp
	Version                   int
	KeyBits                   int
	Type                      string
}

func (l *listCredentialLibraryResult) toLibrary(ctx context.Context) (credential.Library, error) {
	const op = "vault.(*listCredentialLibraryResult).toLibrary"
	switch l.Type {
	case "generic":
		cl := &CredentialLibrary{
			CredentialLibrary: &store.CredentialLibrary{
				PublicId:       l.PublicId,
				StoreId:        l.StoreId,
				Name:           l.Name,
				Description:    l.Description,
				CreateTime:     l.CreateTime,
				UpdateTime:     l.UpdateTime,
				Version:        uint32(l.Version),
				VaultPath:      l.VaultPath,
				CredentialType: l.CredentialType,
				HttpMethod:     l.HttpMethod,
			},
		}
		// Assign byte slices only if the string isn't empty
		if l.HttpRequestBody != "" {
			cl.HttpRequestBody = []byte(l.HttpRequestBody)
		}
		return cl, nil
	case "ssh":
		return &SSHCertificateCredentialLibrary{
			SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{
				PublicId:                  l.PublicId,
				StoreId:                   l.StoreId,
				Name:                      l.Name,
				Description:               l.Description,
				CreateTime:                l.CreateTime,
				UpdateTime:                l.UpdateTime,
				Version:                   uint32(l.Version),
				VaultPath:                 l.VaultPath,
				CredentialType:            l.CredentialType,
				Username:                  l.Username,
				KeyType:                   l.KeyType,
				KeyBits:                   uint32(l.KeyBits),
				Ttl:                       l.Ttl,
				KeyId:                     l.KeyId,
				CriticalOptions:           l.CriticalOptions,
				Extensions:                l.Extensions,
				AdditionalValidPrincipals: l.AdditionalValidPrincipals,
			},
		}, nil
	case "ldap":
		return &LdapCredentialLibrary{
			LdapCredentialLibrary: &store.LdapCredentialLibrary{
				PublicId:       l.PublicId,
				StoreId:        l.StoreId,
				Name:           l.Name,
				Description:    l.Description,
				CreateTime:     l.CreateTime,
				UpdateTime:     l.UpdateTime,
				Version:        uint32(l.Version),
				VaultPath:      l.VaultPath,
				CredentialType: l.CredentialType,
			},
		}, nil
	default:
		return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("unexpected vault credential library type %s returned", l.Type))
	}
}

type deletedCredentialLibrary struct {
	PublicId   string `gorm:"primary_key"`
	DeleteTime *timestamp.Timestamp
}

// TableName returns the tablename to override the default gorm table name
func (s *deletedCredentialLibrary) TableName() string {
	return "credential_vault_generic_library_deleted"
}
