/*
 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2017 MinIO, 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 credentials

import (
	"encoding/json"
	"os"
	"path/filepath"
	"runtime"
)

// A FileMinioClient retrieves credentials from the current user's home
// directory, and keeps track if those credentials are expired.
//
// Configuration file example: $HOME/.mc/config.json
type FileMinioClient struct {
	// Path to the shared credentials file.
	//
	// If empty will look for "MINIO_SHARED_CREDENTIALS_FILE" env variable. If the
	// env value is empty will default to current user's home directory.
	// Linux/OSX: "$HOME/.mc/config.json"
	// Windows:   "%USERALIAS%\mc\config.json"
	Filename string

	// MinIO Alias to extract credentials from the shared credentials file. If empty
	// will default to environment variable "MINIO_ALIAS" or "s3" if
	// environment variable is also not set.
	Alias string

	// retrieved states if the credentials have been successfully retrieved.
	retrieved bool
}

// NewFileMinioClient returns a pointer to a new Credentials object
// wrapping the Alias file provider.
func NewFileMinioClient(filename, alias string) *Credentials {
	return New(&FileMinioClient{
		Filename: filename,
		Alias:    alias,
	})
}

func (p *FileMinioClient) retrieve() (Value, error) {
	if p.Filename == "" {
		if value, ok := os.LookupEnv("MINIO_SHARED_CREDENTIALS_FILE"); ok {
			p.Filename = value
		} else {
			homeDir, err := os.UserHomeDir()
			if err != nil {
				return Value{}, err
			}
			p.Filename = filepath.Join(homeDir, ".mc", "config.json")
			if runtime.GOOS == "windows" {
				p.Filename = filepath.Join(homeDir, "mc", "config.json")
			}
		}
	}

	if p.Alias == "" {
		p.Alias = os.Getenv("MINIO_ALIAS")
		if p.Alias == "" {
			p.Alias = "s3"
		}
	}

	p.retrieved = false

	hostCfg, err := loadAlias(p.Filename, p.Alias)
	if err != nil {
		return Value{}, err
	}

	p.retrieved = true
	return Value{
		AccessKeyID:     hostCfg.AccessKey,
		SecretAccessKey: hostCfg.SecretKey,
		SignerType:      parseSignatureType(hostCfg.API),
	}, nil
}

// Retrieve reads and extracts the shared credentials from the current
// users home directory.
func (p *FileMinioClient) Retrieve() (Value, error) {
	return p.retrieve()
}

// RetrieveWithCredContext - is like Retrieve()
func (p *FileMinioClient) RetrieveWithCredContext(_ *CredContext) (Value, error) {
	return p.retrieve()
}

// IsExpired returns if the shared credentials have expired.
func (p *FileMinioClient) IsExpired() bool {
	return !p.retrieved
}

// hostConfig configuration of a host.
type hostConfig struct {
	URL       string `json:"url"`
	AccessKey string `json:"accessKey"`
	SecretKey string `json:"secretKey"`
	API       string `json:"api"`
}

// config config version.
type config struct {
	Version string                `json:"version"`
	Hosts   map[string]hostConfig `json:"hosts"`
	Aliases map[string]hostConfig `json:"aliases"`
}

// loadAliass loads from the file pointed to by shared credentials filename for alias.
// The credentials retrieved from the alias will be returned or error. Error will be
// returned if it fails to read from the file.
func loadAlias(filename, alias string) (hostConfig, error) {
	cfg := &config{}
	configBytes, err := os.ReadFile(filename)
	if err != nil {
		return hostConfig{}, err
	}
	if err = json.Unmarshal(configBytes, cfg); err != nil {
		return hostConfig{}, err
	}

	if cfg.Version == "10" {
		return cfg.Aliases[alias], nil
	}

	return cfg.Hosts[alias], nil
}
