// Copyright 2022-2025 The sacloud/iaas-api-go Authors
//
// 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 api

import (
	"fmt"
	"os"
	"runtime"

	client "github.com/sacloud/api-client-go"
	"github.com/sacloud/api-client-go/profile"
	"github.com/sacloud/iaas-api-go"
	"github.com/sacloud/packages-go/envvar"
)

var UserAgent = fmt.Sprintf(
	"sacloud/iaas-api-go/v%s (%s/%s; +https://github.com/sacloud/iaas-api-go) %s",
	iaas.Version,
	runtime.GOOS,
	runtime.GOARCH,
	client.DefaultUserAgent,
)

// CallerOptions iaas.APICallerを作成する際のオプション
type CallerOptions struct {
	*client.Options

	APIRootURL  string
	DefaultZone string
	Zones       []string

	TraceAPI      bool
	FakeMode      bool
	FakeStorePath string
}

// DefaultOption 環境変数、プロファイルからCallerOptionsを組み立てて返す
//
// プロファイルは環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`でプロファイル名が指定されていればそちらを優先し、
// 未指定の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。
// 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される
func DefaultOption() (*CallerOptions, error) {
	return DefaultOptionWithProfile("")
}

// DefaultOptionWithProfile 環境変数、プロファイルからCallerOptionsを組み立てて返す
//
// プロファイルは引数を優先し、空の場合は環境変数`SAKURACLOUD_PROFILE`または`USACLOUD_PROFILE`が利用され、
// それも空の場合は通常のプロファイル処理(~/.usacloud/currentファイルから読み込み)される。
// 同じ項目を複数箇所で指定していた場合、環境変数->プロファイルの順で上書きされたものが返される
func DefaultOptionWithProfile(profileName string) (*CallerOptions, error) {
	options, err := client.DefaultOptionWithProfile(profileName)
	if err != nil {
		return nil, err
	}

	fromEnv := OptionsFromEnv()
	fromEnv.Options = options

	fromProfile, err := OptionsFromProfile(profileName)
	if err != nil {
		return nil, err
	}
	fromProfile.Options = options

	defaults := &CallerOptions{
		APIRootURL:  iaas.SakuraCloudAPIRoot,
		DefaultZone: iaas.APIDefaultZone,
		Zones:       iaas.SakuraCloudZones,
		Options: &client.Options{
			UserAgent: UserAgent,
		},
	}

	return MergeOptions(defaults, fromEnv, fromProfile), nil
}

// OptionsFromEnv 環境変数からCallerOptionsを組み立てて返す
func OptionsFromEnv() *CallerOptions {
	return &CallerOptions{
		Options:     client.OptionsFromEnv(),
		APIRootURL:  envvar.StringFromEnv("SAKURACLOUD_API_ROOT_URL", ""),
		DefaultZone: envvar.StringFromEnv("SAKURACLOUD_DEFAULT_ZONE", ""),
		Zones:       envvar.StringSliceFromEnv("SAKURACLOUD_ZONES", []string{}),

		TraceAPI: profile.EnableAPITrace(envvar.StringFromEnv("SAKURACLOUD_TRACE", "")),

		FakeMode:      os.Getenv("SAKURACLOUD_FAKE_MODE") != "",
		FakeStorePath: envvar.StringFromEnv("SAKURACLOUD_FAKE_STORE_PATH", ""),
	}
}

// OptionsFromProfile 指定のプロファイルからCallerOptionsを組み立てて返す
// プロファイル名に空文字が指定された場合はカレントプロファイルが利用される
func OptionsFromProfile(profileName string) (*CallerOptions, error) {
	options, err := client.OptionsFromProfile(profileName)
	if err != nil {
		return nil, err
	}
	config := options.ProfileConfigValue()
	return &CallerOptions{
		Options:       options,
		APIRootURL:    config.APIRootURL,
		DefaultZone:   config.DefaultZone,
		Zones:         config.Zones,
		TraceAPI:      config.EnableAPITrace(),
		FakeMode:      config.FakeMode,
		FakeStorePath: config.FakeStorePath,
	}, nil
}

// MergeOptions 指定のCallerOptionsの非ゼロ値フィールドをoのコピーにマージして返す
func MergeOptions(opts ...*CallerOptions) *CallerOptions {
	merged := &CallerOptions{}
	for _, opt := range opts {
		if opt.Options != nil {
			var opts []*client.Options
			if merged.Options != nil {
				opts = append(opts, merged.Options)
			}
			opts = append(opts, opt.Options)
			merged.Options = client.MergeOptions(opts...)
		}
		if opt.APIRootURL != "" {
			merged.APIRootURL = opt.APIRootURL
		}
		if opt.DefaultZone != "" {
			merged.DefaultZone = opt.DefaultZone
		}
		if len(opt.Zones) > 0 {
			merged.Zones = opt.Zones
		}

		// Note: bool値は一度trueにしたらMergeでfalseになることがない
		if opt.TraceAPI {
			merged.TraceAPI = true
		}
		if opt.FakeMode {
			merged.FakeMode = true
		}
		if opt.FakeStorePath != "" {
			merged.FakeStorePath = opt.FakeStorePath
		}
	}
	return merged
}
