package codersdk

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"golang.org/x/xerrors"
)

// DeploymentConfig is the central configuration for the coder server.
type DeploymentConfig struct {
	AccessURL                       *DeploymentConfigField[string]          `json:"access_url" typescript:",notnull"`
	WildcardAccessURL               *DeploymentConfigField[string]          `json:"wildcard_access_url" typescript:",notnull"`
	HTTPAddress                     *DeploymentConfigField[string]          `json:"http_address" typescript:",notnull"`
	AutobuildPollInterval           *DeploymentConfigField[time.Duration]   `json:"autobuild_poll_interval" typescript:",notnull"`
	DERP                            *DERP                                   `json:"derp" typescript:",notnull"`
	GitAuth                         *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"`
	Prometheus                      *PrometheusConfig                       `json:"prometheus" typescript:",notnull"`
	Pprof                           *PprofConfig                            `json:"pprof" typescript:",notnull"`
	ProxyTrustedHeaders             *DeploymentConfigField[[]string]        `json:"proxy_trusted_headers" typescript:",notnull"`
	ProxyTrustedOrigins             *DeploymentConfigField[[]string]        `json:"proxy_trusted_origins" typescript:",notnull"`
	CacheDirectory                  *DeploymentConfigField[string]          `json:"cache_directory" typescript:",notnull"`
	InMemoryDatabase                *DeploymentConfigField[bool]            `json:"in_memory_database" typescript:",notnull"`
	PostgresURL                     *DeploymentConfigField[string]          `json:"pg_connection_url" typescript:",notnull"`
	OAuth2                          *OAuth2Config                           `json:"oauth2" typescript:",notnull"`
	OIDC                            *OIDCConfig                             `json:"oidc" typescript:",notnull"`
	Telemetry                       *TelemetryConfig                        `json:"telemetry" typescript:",notnull"`
	TLS                             *TLSConfig                              `json:"tls" typescript:",notnull"`
	Trace                           *TraceConfig                            `json:"trace" typescript:",notnull"`
	SecureAuthCookie                *DeploymentConfigField[bool]            `json:"secure_auth_cookie" typescript:",notnull"`
	SSHKeygenAlgorithm              *DeploymentConfigField[string]          `json:"ssh_keygen_algorithm" typescript:",notnull"`
	MetricsCacheRefreshInterval     *DeploymentConfigField[time.Duration]   `json:"metrics_cache_refresh_interval" typescript:",notnull"`
	AgentStatRefreshInterval        *DeploymentConfigField[time.Duration]   `json:"agent_stat_refresh_interval" typescript:",notnull"`
	AgentFallbackTroubleshootingURL *DeploymentConfigField[string]          `json:"agent_fallback_troubleshooting_url" typescript:",notnull"`
	AuditLogging                    *DeploymentConfigField[bool]            `json:"audit_logging" typescript:",notnull"`
	BrowserOnly                     *DeploymentConfigField[bool]            `json:"browser_only" typescript:",notnull"`
	SCIMAPIKey                      *DeploymentConfigField[string]          `json:"scim_api_key" typescript:",notnull"`
	Provisioner                     *ProvisionerConfig                      `json:"provisioner" typescript:",notnull"`
	RateLimit                       *RateLimitConfig                        `json:"rate_limit" typescript:",notnull"`
	Experiments                     *DeploymentConfigField[[]string]        `json:"experiments" typescript:",notnull"`
	UpdateCheck                     *DeploymentConfigField[bool]            `json:"update_check" typescript:",notnull"`
	MaxTokenLifetime                *DeploymentConfigField[time.Duration]   `json:"max_token_lifetime" typescript:",notnull"`
	Swagger                         *SwaggerConfig                          `json:"swagger" typescript:",notnull"`
	Logging                         *LoggingConfig                          `json:"logging" typescript:",notnull"`
	Dangerous                       *DangerousConfig                        `json:"dangerous" typescript:",notnull"`
	DisablePathApps                 *DeploymentConfigField[bool]            `json:"disable_path_apps" typescript:",notnull"`

	// DEPRECATED: Use HTTPAddress or TLS.Address instead.
	Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
	// DEPRECATED: Use Experiments instead.
	Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
}

type DERP struct {
	Server *DERPServerConfig `json:"server" typescript:",notnull"`
	Config *DERPConfig       `json:"config" typescript:",notnull"`
}

type DERPServerConfig struct {
	Enable        *DeploymentConfigField[bool]     `json:"enable" typescript:",notnull"`
	RegionID      *DeploymentConfigField[int]      `json:"region_id" typescript:",notnull"`
	RegionCode    *DeploymentConfigField[string]   `json:"region_code" typescript:",notnull"`
	RegionName    *DeploymentConfigField[string]   `json:"region_name" typescript:",notnull"`
	STUNAddresses *DeploymentConfigField[[]string] `json:"stun_addresses" typescript:",notnull"`
	RelayURL      *DeploymentConfigField[string]   `json:"relay_url" typescript:",notnull"`
}

type DERPConfig struct {
	URL  *DeploymentConfigField[string] `json:"url" typescript:",notnull"`
	Path *DeploymentConfigField[string] `json:"path" typescript:",notnull"`
}

type PrometheusConfig struct {
	Enable  *DeploymentConfigField[bool]   `json:"enable" typescript:",notnull"`
	Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
}

type PprofConfig struct {
	Enable  *DeploymentConfigField[bool]   `json:"enable" typescript:",notnull"`
	Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
}

type OAuth2Config struct {
	Github *OAuth2GithubConfig `json:"github" typescript:",notnull"`
}

type OAuth2GithubConfig struct {
	ClientID          *DeploymentConfigField[string]   `json:"client_id" typescript:",notnull"`
	ClientSecret      *DeploymentConfigField[string]   `json:"client_secret" typescript:",notnull"`
	AllowedOrgs       *DeploymentConfigField[[]string] `json:"allowed_orgs" typescript:",notnull"`
	AllowedTeams      *DeploymentConfigField[[]string] `json:"allowed_teams" typescript:",notnull"`
	AllowSignups      *DeploymentConfigField[bool]     `json:"allow_signups" typescript:",notnull"`
	AllowEveryone     *DeploymentConfigField[bool]     `json:"allow_everyone" typescript:",notnull"`
	EnterpriseBaseURL *DeploymentConfigField[string]   `json:"enterprise_base_url" typescript:",notnull"`
}

type OIDCConfig struct {
	AllowSignups        *DeploymentConfigField[bool]     `json:"allow_signups" typescript:",notnull"`
	ClientID            *DeploymentConfigField[string]   `json:"client_id" typescript:",notnull"`
	ClientSecret        *DeploymentConfigField[string]   `json:"client_secret" typescript:",notnull"`
	EmailDomain         *DeploymentConfigField[[]string] `json:"email_domain" typescript:",notnull"`
	IssuerURL           *DeploymentConfigField[string]   `json:"issuer_url" typescript:",notnull"`
	Scopes              *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"`
	IgnoreEmailVerified *DeploymentConfigField[bool]     `json:"ignore_email_verified" typescript:",notnull"`
	UsernameField       *DeploymentConfigField[string]   `json:"username_field" typescript:",notnull"`
}

type TelemetryConfig struct {
	Enable *DeploymentConfigField[bool]   `json:"enable" typescript:",notnull"`
	Trace  *DeploymentConfigField[bool]   `json:"trace" typescript:",notnull"`
	URL    *DeploymentConfigField[string] `json:"url" typescript:",notnull"`
}

type TLSConfig struct {
	Enable         *DeploymentConfigField[bool]     `json:"enable" typescript:",notnull"`
	Address        *DeploymentConfigField[string]   `json:"address" typescript:",notnull"`
	RedirectHTTP   *DeploymentConfigField[bool]     `json:"redirect_http" typescript:",notnull"`
	CertFiles      *DeploymentConfigField[[]string] `json:"cert_file" typescript:",notnull"`
	ClientAuth     *DeploymentConfigField[string]   `json:"client_auth" typescript:",notnull"`
	ClientCAFile   *DeploymentConfigField[string]   `json:"client_ca_file" typescript:",notnull"`
	KeyFiles       *DeploymentConfigField[[]string] `json:"key_file" typescript:",notnull"`
	MinVersion     *DeploymentConfigField[string]   `json:"min_version" typescript:",notnull"`
	ClientCertFile *DeploymentConfigField[string]   `json:"client_cert_file" typescript:",notnull"`
	ClientKeyFile  *DeploymentConfigField[string]   `json:"client_key_file" typescript:",notnull"`
}

type TraceConfig struct {
	Enable          *DeploymentConfigField[bool]   `json:"enable" typescript:",notnull"`
	HoneycombAPIKey *DeploymentConfigField[string] `json:"honeycomb_api_key" typescript:",notnull"`
	CaptureLogs     *DeploymentConfigField[bool]   `json:"capture_logs" typescript:",notnull"`
}

type GitAuthConfig struct {
	ID           string   `json:"id"`
	Type         string   `json:"type"`
	ClientID     string   `json:"client_id"`
	ClientSecret string   `json:"-" yaml:"client_secret"`
	AuthURL      string   `json:"auth_url"`
	TokenURL     string   `json:"token_url"`
	ValidateURL  string   `json:"validate_url"`
	Regex        string   `json:"regex"`
	NoRefresh    bool     `json:"no_refresh"`
	Scopes       []string `json:"scopes"`
}

type ProvisionerConfig struct {
	Daemons             *DeploymentConfigField[int]           `json:"daemons" typescript:",notnull"`
	DaemonPollInterval  *DeploymentConfigField[time.Duration] `json:"daemon_poll_interval" typescript:",notnull"`
	DaemonPollJitter    *DeploymentConfigField[time.Duration] `json:"daemon_poll_jitter" typescript:",notnull"`
	ForceCancelInterval *DeploymentConfigField[time.Duration] `json:"force_cancel_interval" typescript:",notnull"`
}

type RateLimitConfig struct {
	DisableAll *DeploymentConfigField[bool] `json:"disable_all" typescript:",notnull"`
	API        *DeploymentConfigField[int]  `json:"api" typescript:",notnull"`
}

type SwaggerConfig struct {
	Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
}

type LoggingConfig struct {
	Human       *DeploymentConfigField[string] `json:"human" typescript:",notnull"`
	JSON        *DeploymentConfigField[string] `json:"json" typescript:",notnull"`
	Stackdriver *DeploymentConfigField[string] `json:"stackdriver" typescript:",notnull"`
}

type DangerousConfig struct {
	AllowPathAppSharing         *DeploymentConfigField[bool] `json:"allow_path_app_sharing" typescript:",notnull"`
	AllowPathAppSiteOwnerAccess *DeploymentConfigField[bool] `json:"allow_path_app_site_owner_access" typescript:",notnull"`
}

type Flaggable interface {
	string | time.Duration | bool | int | []string | []GitAuthConfig
}

type DeploymentConfigField[T Flaggable] struct {
	Name  string `json:"name"`
	Usage string `json:"usage"`
	Flag  string `json:"flag"`
	// EnvOverride will override the automatically generated environment
	// variable name. Useful if you're moving values around but need to keep
	// backwards compatibility with old environment variable names.
	//
	// NOTE: this is not supported for array flags.
	EnvOverride string `json:"-"`
	Shorthand   string `json:"shorthand"`
	Enterprise  bool   `json:"enterprise"`
	Hidden      bool   `json:"hidden"`
	Secret      bool   `json:"secret"`
	Default     T      `json:"default"`
	Value       T      `json:"value"`
}

// MarshalJSON removes the Value field from the JSON output of any fields marked Secret.
// nolint:revive
func (f *DeploymentConfigField[T]) MarshalJSON() ([]byte, error) {
	copy := struct {
		Name       string `json:"name"`
		Usage      string `json:"usage"`
		Flag       string `json:"flag"`
		Shorthand  string `json:"shorthand"`
		Enterprise bool   `json:"enterprise"`
		Hidden     bool   `json:"hidden"`
		Secret     bool   `json:"secret"`
		Default    T      `json:"default"`
		Value      T      `json:"value"`
	}{
		Name:       f.Name,
		Usage:      f.Usage,
		Flag:       f.Flag,
		Shorthand:  f.Shorthand,
		Enterprise: f.Enterprise,
		Hidden:     f.Hidden,
		Secret:     f.Secret,
	}

	if !f.Secret {
		copy.Default = f.Default
		copy.Value = f.Value
	}

	return json.Marshal(copy)
}

// DeploymentConfig returns the deployment config for the coder server.
func (c *Client) DeploymentConfig(ctx context.Context) (DeploymentConfig, error) {
	res, err := c.Request(ctx, http.MethodGet, "/api/v2/config/deployment", nil)
	if err != nil {
		return DeploymentConfig{}, xerrors.Errorf("execute request: %w", err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return DeploymentConfig{}, readBodyAsError(res)
	}

	var df DeploymentConfig
	return df, json.NewDecoder(res.Body).Decode(&df)
}
