// Copyright 2026 printing-press-golden. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.

package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/pelletier/go-toml/v2"
)

type Config struct {
	BaseURL               string            `toml:"base_url"`
	AuthHeaderVal         string            `toml:"auth_header"`
	Headers               map[string]string `toml:"headers,omitempty"`
	AuthSource            string            `toml:"-"`
	AccessToken           string            `toml:"access_token"`
	RefreshToken          string            `toml:"refresh_token"`
	TokenExpiry           time.Time         `toml:"token_expiry"`
	ClientID              string            `toml:"client_id"`
	ClientSecret          string            `toml:"client_secret"`
	Path                  string            `toml:"-"`
	RichAuthApiKey        string            `toml:"auth_api_key"`
	RichAuthClientId      string            `toml:"auth_client_id"`
	RichAuthClientSecret  string            `toml:"auth_client_secret"`
	RichAuthSessionCookie string            `toml:"auth_session_cookie"`
	RichAuthOptionalToken string            `toml:"auth_optional_token"`
	RichAuthBotToken      string            `toml:"auth_bot_token"`
	RichAuthUserToken     string            `toml:"auth_user_token"`
}

func Load(configPath string) (*Config, error) {
	cfg := &Config{
		BaseURL: "https://api.rich-auth.example/v1",
	}

	// Resolve config path
	path := configPath
	if path == "" {
		path = os.Getenv("PRINTING_PRESS_RICH_CONFIG")
	}
	if path == "" {
		home, _ := os.UserHomeDir()
		path = filepath.Join(home, ".config", "printing-press-rich-pp-cli", "config.toml")
	}
	cfg.Path = path

	// Try to load config file
	data, err := os.ReadFile(path)
	if err == nil {
		if err := toml.Unmarshal(data, cfg); err != nil {
			return nil, fmt.Errorf("parsing config %s: %w", path, err)
		}
	}

	// Env var overrides
	if v := os.Getenv("RICH_AUTH_API_KEY"); v != "" {
		cfg.RichAuthApiKey = v
		cfg.AuthSource = "env:RICH_AUTH_API_KEY"
	}
	if v := os.Getenv("RICH_AUTH_CLIENT_ID"); v != "" {
		cfg.RichAuthClientId = v
		cfg.AuthSource = "env:RICH_AUTH_CLIENT_ID"
	}
	if v := os.Getenv("RICH_AUTH_CLIENT_SECRET"); v != "" {
		cfg.RichAuthClientSecret = v
		cfg.AuthSource = "env:RICH_AUTH_CLIENT_SECRET"
	}
	if v := os.Getenv("RICH_AUTH_SESSION_COOKIE"); v != "" {
		cfg.RichAuthSessionCookie = v
		cfg.AuthSource = "env:RICH_AUTH_SESSION_COOKIE"
	}
	if v := os.Getenv("RICH_AUTH_OPTIONAL_TOKEN"); v != "" {
		cfg.RichAuthOptionalToken = v
		cfg.AuthSource = "env:RICH_AUTH_OPTIONAL_TOKEN"
	}
	if v := os.Getenv("RICH_AUTH_BOT_TOKEN"); v != "" {
		cfg.RichAuthBotToken = v
		cfg.AuthSource = "env:RICH_AUTH_BOT_TOKEN"
	}
	if v := os.Getenv("RICH_AUTH_USER_TOKEN"); v != "" {
		cfg.RichAuthUserToken = v
		cfg.AuthSource = "env:RICH_AUTH_USER_TOKEN"
	}

	// Label config-file-derived credentials so doctor can distinguish
	// "credentials persisted on disk" from "no credentials at all" — without
	// this, users who saved via set-token without an env var see a blank
	// auth_source and can't tell whether their config is being picked up.
	// The label is the literal "config" rather than "config:<path>"; the
	// config file path is exposed separately as report["config_path"], and
	// embedding it in auth_source leaks the user's home directory through
	// doctor's JSON envelope.
	if cfg.AuthSource == "" && (cfg.AuthHeaderVal != "" || cfg.AccessToken != "") {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthApiKey != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthClientId != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthClientSecret != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthSessionCookie != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthOptionalToken != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthBotToken != "" {
		cfg.AuthSource = "config"
	}
	if cfg.AuthSource == "" && cfg.RichAuthUserToken != "" {
		cfg.AuthSource = "config"
	}

	// Base URL override (used by printing-press verify to point at mock/test servers)
	if v := os.Getenv("PRINTING_PRESS_RICH_BASE_URL"); v != "" {
		cfg.BaseURL = v
	}
	return cfg, nil
}

func (c *Config) AuthHeader() string {
	if c.AuthHeaderVal != "" {
		return c.AuthHeaderVal
	}
	token := c.RichAuthApiKey
	if token == "" {
		return ""
	}
	if c.RichAuthApiKey == "" {
		return ""
	}
	return token
}

func applyAuthFormat(format string, replacements map[string]string) string {
	if format == "" {
		return ""
	}
	for key, value := range replacements {
		format = strings.ReplaceAll(format, "{"+key+"}", value)
	}
	if strings.Contains(format, "{") {
		return ""
	}
	return format
}

func (c *Config) SaveTokens(clientID, clientSecret, accessToken, refreshToken string, expiry time.Time) error {
	c.ClientID = clientID
	c.ClientSecret = clientSecret
	c.AccessToken = accessToken
	c.RefreshToken = refreshToken
	c.TokenExpiry = expiry
	return c.save()
}

// SaveCredential persists a single API credential to the field that
// AuthHeader() consults for api_key auth. Writing to AccessToken (the
// bearer slot) would silently no-op since AuthHeader() reads the env-var-
// derived field, not AccessToken, when Auth.Type == "api_key".
//
// The clears precede the assignment so a canonical env-var whose placeholder
// collides with a builtin tag (e.g. an env var named XXX_ACCESS_TOKEN
// resolving to the AccessToken field) ends up holding the new token.
func (c *Config) SaveCredential(token string) error {
	c.AuthHeaderVal = ""
	c.AccessToken = ""
	c.RichAuthApiKey = token
	return c.save()
}

func (c *Config) ClearTokens() error {
	// AuthHeader() falls back to the env-var-derived fields when AuthHeaderVal
	// and AccessToken are empty, so dropping the working credential requires
	// zeroing every emitted credential field, not just the OAuth trio.
	// ClientID/ClientSecret persist to disk via SaveTokens for the oauth2
	// and oauth2-cc flows, so logout must wipe them too; otherwise
	// `auth login` can re-mint a new access token unattended.
	c.AuthHeaderVal = ""
	c.AccessToken = ""
	c.RefreshToken = ""
	c.TokenExpiry = time.Time{}
	c.ClientID = ""
	c.ClientSecret = ""
	c.RichAuthApiKey = ""
	c.RichAuthClientId = ""
	c.RichAuthClientSecret = ""
	c.RichAuthSessionCookie = ""
	c.RichAuthOptionalToken = ""
	c.RichAuthBotToken = ""
	c.RichAuthUserToken = ""
	return c.save()
}

func (c *Config) save() error {
	dir := filepath.Dir(c.Path)
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("creating config dir: %w", err)
	}
	data, err := toml.Marshal(c)
	if err != nil {
		return fmt.Errorf("marshaling config: %w", err)
	}
	return os.WriteFile(c.Path, data, 0o600)
}

// Ensure strings import is used
var _ = strings.ReplaceAll
