//
// Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

package madmin

import (
	"encoding/json"
	"errors"
	"log"
)

//go:generate msgp -file $GOFILE

// TierConfigVer refers to the current tier config version
const TierConfigVer = "v1"

// TierType enumerates different remote tier backends.
type TierType int

const (
	// Unsupported refers to remote tier backend that is not supported in this version
	Unsupported TierType = iota
	// S3 refers to AWS S3 compatible backend
	S3
	// Azure refers to Azure Blob Storage
	Azure
	// GCS refers to Google Cloud Storage
	GCS
	// MinIO refers to MinIO object storage backend
	MinIO
)

// String returns the name of tt's remote tier backend.
func (tt TierType) String() string {
	switch tt {
	case S3:
		return "s3"
	case Azure:
		return "azure"
	case GCS:
		return "gcs"
	case MinIO:
		return "minio"
	}
	return "unsupported"
}

// MarshalJSON returns the canonical json representation of tt.
func (tt TierType) MarshalJSON() ([]byte, error) {
	typ := tt.String()
	return json.Marshal(typ)
}

// UnmarshalJSON parses the provided tier type string, failing unmarshal
// if data contains invalid tier type.
func (tt *TierType) UnmarshalJSON(data []byte) error {
	var s string
	err := json.Unmarshal(data, &s)
	if err != nil {
		return err
	}

	newtt, err := NewTierType(s)
	if err != nil {
		return err
	}
	*tt = newtt
	return nil
}

// NewTierType creates TierType if scType is a valid tier type string, otherwise
// returns an error.
func NewTierType(scType string) (TierType, error) {
	switch scType {
	case S3.String():
		return S3, nil
	case Azure.String():
		return Azure, nil
	case GCS.String():
		return GCS, nil
	case MinIO.String():
		return MinIO, nil
	}

	return Unsupported, ErrTierTypeUnsupported
}

// TierConfig represents the different remote tier backend configurations
// supported. The specific backend is identified by the Type field. It has a
// Version field to allow for backwards-compatible extension in the future.
type TierConfig struct {
	Version string
	Type    TierType   `json:",omitempty"`
	Name    string     `json:",omitempty"`
	S3      *TierS3    `json:",omitempty"`
	Azure   *TierAzure `json:",omitempty"`
	GCS     *TierGCS   `json:",omitempty"`
	MinIO   *TierMinIO `json:",omitempty"`
}

var (
	// ErrTierNameEmpty "remote tier name empty"
	ErrTierNameEmpty = errors.New("remote tier name empty")
	// ErrTierInvalidConfig "invalid tier config"
	ErrTierInvalidConfig = errors.New("invalid tier config")
	// ErrTierInvalidConfigVersion "invalid tier config version"
	ErrTierInvalidConfigVersion = errors.New("invalid tier config version")
	// ErrTierTypeUnsupported "unsupported tier type"
	ErrTierTypeUnsupported = errors.New("unsupported tier type")
)

// Clone returns a copy of TierConfig with secret key/credentials redacted.
func (cfg *TierConfig) Clone() TierConfig {
	var (
		s3  TierS3
		az  TierAzure
		gcs TierGCS
		m   TierMinIO
	)
	switch cfg.Type {
	case S3:
		s3 = *cfg.S3
		s3.SecretKey = "REDACTED"
	case Azure:
		az = *cfg.Azure
		az.AccountKey = "REDACTED"
	case GCS:
		gcs = *cfg.GCS
		gcs.Creds = "REDACTED"
	case MinIO:
		m = *cfg.MinIO
		m.SecretKey = "REDACTED"
	}
	return TierConfig{
		Version: cfg.Version,
		Type:    cfg.Type,
		Name:    cfg.Name,
		S3:      &s3,
		Azure:   &az,
		GCS:     &gcs,
		MinIO:   &m,
	}
}

// UnmarshalJSON unmarshals json value to ensure that Type field is filled in
// correspondence with the tier config supplied.
// See TestUnmarshalTierConfig for an example json.
func (cfg *TierConfig) UnmarshalJSON(b []byte) error {
	type tierConfig TierConfig
	var m tierConfig
	err := json.Unmarshal(b, &m)
	if err != nil {
		return err
	}

	switch m.Version {
	case TierConfigVer:
	default:
		return ErrTierInvalidConfigVersion
	}

	switch m.Type {
	case S3:
		if m.S3 == nil {
			return ErrTierInvalidConfig
		}
	case Azure:
		if m.Azure == nil {
			return ErrTierInvalidConfig
		}
	case GCS:
		if m.GCS == nil {
			return ErrTierInvalidConfig
		}
	case MinIO:
		if m.MinIO == nil {
			return ErrTierInvalidConfig
		}
	}

	if m.Name == "" {
		return ErrTierNameEmpty
	}

	*cfg = TierConfig(m)
	return nil
}

// Endpoint returns the remote tier backend endpoint.
func (cfg *TierConfig) Endpoint() string {
	switch cfg.Type {
	case S3:
		return cfg.S3.Endpoint
	case Azure:
		return cfg.Azure.Endpoint
	case GCS:
		return cfg.GCS.Endpoint
	case MinIO:
		return cfg.MinIO.Endpoint
	}
	log.Printf("unexpected tier type %s", cfg.Type)
	return ""
}

// Bucket returns the remote tier backend bucket.
func (cfg *TierConfig) Bucket() string {
	switch cfg.Type {
	case S3:
		return cfg.S3.Bucket
	case Azure:
		return cfg.Azure.Bucket
	case GCS:
		return cfg.GCS.Bucket
	case MinIO:
		return cfg.MinIO.Bucket
	}
	log.Printf("unexpected tier type %s", cfg.Type)
	return ""
}

// Prefix returns the remote tier backend prefix.
func (cfg *TierConfig) Prefix() string {
	switch cfg.Type {
	case S3:
		return cfg.S3.Prefix
	case Azure:
		return cfg.Azure.Prefix
	case GCS:
		return cfg.GCS.Prefix
	case MinIO:
		return cfg.MinIO.Prefix
	}
	log.Printf("unexpected tier type %s", cfg.Type)
	return ""
}

// Region returns the remote tier backend region.
func (cfg *TierConfig) Region() string {
	switch cfg.Type {
	case S3:
		return cfg.S3.Region
	case Azure:
		return cfg.Azure.Region
	case GCS:
		return cfg.GCS.Region
	case MinIO:
		return cfg.MinIO.Region
	}
	log.Printf("unexpected tier type %s", cfg.Type)
	return ""
}
