package config

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/joho/godotenv"
	"go.uber.org/zap"
	"gopkg.in/yaml.v2"
)

const (
	// MicroDollarsPerDollar represents the conversion factor from dollars to micro-dollars
	MicroDollarsPerDollar = 1_000_000
)

// DollarsToMicroDollarsForTest converts dollars to micro-dollars
// Should be only used in tests!
func DollarsToMicroDollarsForTest(dollars float64) int64 {
	return int64(dollars * MicroDollarsPerDollar)
}

// MicroDollarsToDollarsForDisplay converts micro-dollars to dollars
// Should be only used for display!
func MicroDollarsToDollarsForDisplay(microDollars int64) float64 {
	return float64(microDollars) / MicroDollarsPerDollar
}

// Config represents the application configuration
type Config struct {
	Server             ServerConfig             `yaml:"server"`
	EmbeddingProviders EmbeddingProvidersConfig `yaml:"embeddingProviders"`
	Storage            StorageConfig            `yaml:"storage"`
	Quotas             QuotasConfig             `yaml:"quotas"`
	Models             ModelsConfig             `yaml:"models"`
}

// ServerConfig contains server-related configuration
type ServerConfig struct {
	HTTPListenAddr        string `yaml:"httpListenAddr"`
	MetricsPort           int    `yaml:"metricsPort"`
	LogLevel              string `yaml:"logLevel"`
	CheckServiceAPIKey    string `yaml:"checkServiceAPIKey"`    // Can be overridden by env var
	CheckManagementAPIKey string `yaml:"checkManagementAPIKey"` // Can be overridden by env var
}

// EmbeddingProvidersConfig contains embedding provider configurations
type EmbeddingProvidersConfig struct {
	AWSBedrock AWSBedrockConfig `yaml:"awsBedrock"`
}

// AWSBedrockConfig contains AWS Bedrock-related configuration
type AWSBedrockConfig struct {
	Region   string `yaml:"region"`
	APIToken string `yaml:"apiToken"` // Can be overridden by env var
}

// StorageConfig contains storage-related configuration
type StorageConfig struct {
	UsageDataDir string `yaml:"usageDataDir"` // Directory for usage data storage
}

// QuotasConfig contains quota management configuration
type QuotasConfig struct {
	DefaultMonthlyLimit int64            `yaml:"defaultMonthlyLimit"` // in micro-dollars
	SpecialLimits       map[string]int64 `yaml:"specialLimits"`       // in micro-dollars
}

// ModelsConfig contains model definitions and pricing
type ModelsConfig map[string]ModelConfig

// ModelConfig represents a single model configuration
type ModelConfig struct {
	BedrockModelID       string   `yaml:"bedrockModelId"`
	BedrockInvokeModelID string   `yaml:"bedrockInvokeModelId"` // Optional override for InvokeModel's modelId (e.g. inference profile ID/ARN)
	MicroDollarPerKToken int      `yaml:"microDollarPerKToken"` // price per K token in micro-dollars
	AllowedOpts          []string `yaml:"allowedOpts"`          // whitelist of other options this model accepts
}

// IsTitanModel returns true if this is a Titan model
func (m *ModelConfig) IsTitanModel() bool {
	return strings.HasPrefix(m.BedrockModelID, "amazon.titan-")
}

// IsCohereModel returns true if this is a Cohere model
func (m *ModelConfig) IsCohereModel() bool {
	return strings.HasPrefix(m.BedrockModelID, "cohere.")
}

var globalConfig *Config

// Global returns the global configuration instance
func Global() *Config {
	if globalConfig == nil {
		panic("config not initialized")
	}
	return globalConfig
}

// LoadConfig loads configuration from file and environment variables
func LoadConfig(configFile string) error {
	// Set defaults
	config := &Config{
		Server: ServerConfig{
			HTTPListenAddr: ":8080",
			MetricsPort:    9090,
			LogLevel:       "info",
		},
		EmbeddingProviders: EmbeddingProvidersConfig{
			AWSBedrock: AWSBedrockConfig{
				Region: "us-west-2",
			},
		},
		Storage: StorageConfig{
			UsageDataDir: "./data",
		},
		Quotas: QuotasConfig{
			DefaultMonthlyLimit: 25_000_000, // $25.00 in micro-dollars
			SpecialLimits:       make(map[string]int64),
		},
		Models: ModelsConfig{
			"amazon/titan-embed-text-v2": {
				BedrockModelID:       "amazon.titan-embed-text-v2:0",
				MicroDollarPerKToken: 20,
				AllowedOpts:          []string{"dimensions", "normalize"},
			},
			"cohere/embed-english-v3": {
				BedrockModelID:       "cohere.embed-english-v3",
				MicroDollarPerKToken: 100,
				AllowedOpts:          []string{"input_type", "truncate"},
			},
			"cohere/embed-multilingual-v3": {
				BedrockModelID:       "cohere.embed-multilingual-v3",
				MicroDollarPerKToken: 100,
				AllowedOpts:          []string{"input_type", "truncate"},
			},
		},
	}

	// Load from file if provided
	if configFile != "" {
		data, err := os.ReadFile(configFile)
		if err != nil {
			return fmt.Errorf("failed to read config file: %w", err)
		}

		if err := yaml.Unmarshal(data, config); err != nil {
			return fmt.Errorf("failed to parse config file: %w", err)
		}
	}

	var err error
	if testing.Testing() {
		// In test, always load .env from the project root directory, because
		// current working directory may not be the project root.
		_, filename, _, _ := runtime.Caller(0)
		projectRoot := filepath.Join(filepath.Dir(filename), "../../")
		err = godotenv.Load(filepath.Join(projectRoot, ".env"))
	} else {
		err = godotenv.Load(".env")
	}
	if err != nil && !os.IsNotExist(err) {
		zap.L().Error("Error loading .env file", zap.Error(err))
	}
	if err == nil {
		zap.L().Info("Loaded environment variables from .env file")
	}

	// Override with environment variables
	if token := os.Getenv("AWS_BEARER_TOKEN_BEDROCK"); token != "" {
		config.EmbeddingProviders.AWSBedrock.APIToken = token
	}
	if serviceKey := os.Getenv("INFERENCE_SERVICE_API_KEY"); serviceKey != "" {
		config.Server.CheckServiceAPIKey = serviceKey
	}
	if managementKey := os.Getenv("INFERENCE_MANAGEMENT_API_KEY"); managementKey != "" {
		config.Server.CheckManagementAPIKey = managementKey
	}

	globalConfig = config
	return nil
}

// maskSensitiveValue masks a sensitive value, showing the last 4 characters if the value is longer than 4 characters
func maskSensitiveValue(value string) string {
	if value == "" {
		return ""
	}
	if len(value) > 4 {
		return "******" + value[len(value)-4:]
	}
	return "******"
}

// ForPrint returns a copy of the config with sensitive information masked
// This is useful for logging or displaying the config without exposing sensitive data.
func (c *Config) ForPrint() Config {
	newC := *c
	// Mask sensitive information, showing last 4 characters
	newC.EmbeddingProviders.AWSBedrock.APIToken = maskSensitiveValue(newC.EmbeddingProviders.AWSBedrock.APIToken)
	newC.Server.CheckServiceAPIKey = maskSensitiveValue(newC.Server.CheckServiceAPIKey)
	newC.Server.CheckManagementAPIKey = maskSensitiveValue(newC.Server.CheckManagementAPIKey)
	return newC
}
