package postgres

import (
	"strings"
	"testing"
	"time"
)

func TestDefaultConfigReturnsExpectedTuning(t *testing.T) {
	t.Parallel()

	cfg := DefaultConfig()
	if cfg.OperationTimeout != DefaultOperationTimeout {
		t.Fatalf("operation timeout = %v, want %v", cfg.OperationTimeout, DefaultOperationTimeout)
	}
	if cfg.MaxOpenConns != DefaultMaxOpenConns {
		t.Fatalf("max open conns = %d, want %d", cfg.MaxOpenConns, DefaultMaxOpenConns)
	}
	if cfg.MaxIdleConns != DefaultMaxIdleConns {
		t.Fatalf("max idle conns = %d, want %d", cfg.MaxIdleConns, DefaultMaxIdleConns)
	}
	if cfg.ConnMaxLifetime != DefaultConnMaxLifetime {
		t.Fatalf("conn max lifetime = %v, want %v", cfg.ConnMaxLifetime, DefaultConnMaxLifetime)
	}
}

func TestConfigValidateAcceptsHappyPath(t *testing.T) {
	t.Parallel()

	cfg := DefaultConfig()
	cfg.PrimaryDSN = "postgres://localhost:5432/galaxy?sslmode=disable"
	if err := cfg.Validate(); err != nil {
		t.Fatalf("validate happy path: %v", err)
	}
}

func TestConfigValidateRejectsInvalidValues(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		mutate  func(*Config)
		wantSub string
	}{
		{
			name: "missing primary",
			mutate: func(c *Config) {
				c.PrimaryDSN = ""
			},
			wantSub: "primary DSN",
		},
		{
			name: "blank replica entry",
			mutate: func(c *Config) {
				c.ReplicaDSNs = []string{"postgres://a", "   "}
			},
			wantSub: "replica DSN",
		},
		{
			name: "non-positive timeout",
			mutate: func(c *Config) {
				c.OperationTimeout = 0
			},
			wantSub: "operation timeout",
		},
		{
			name: "non-positive max open",
			mutate: func(c *Config) {
				c.MaxOpenConns = 0
			},
			wantSub: "max open conns",
		},
		{
			name: "negative max idle",
			mutate: func(c *Config) {
				c.MaxIdleConns = -1
			},
			wantSub: "max idle conns must not be negative",
		},
		{
			name: "max idle exceeds open",
			mutate: func(c *Config) {
				c.MaxOpenConns = 4
				c.MaxIdleConns = 5
			},
			wantSub: "must not exceed",
		},
		{
			name: "non-positive lifetime",
			mutate: func(c *Config) {
				c.ConnMaxLifetime = 0
			},
			wantSub: "conn max lifetime",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			cfg := DefaultConfig()
			cfg.PrimaryDSN = "postgres://localhost"
			tt.mutate(&cfg)

			err := cfg.Validate()
			if err == nil {
				t.Fatalf("expected validate error, got nil")
			}
			if !strings.Contains(err.Error(), tt.wantSub) {
				t.Fatalf("error %q does not contain %q", err, tt.wantSub)
			}
		})
	}
}

func TestLoadFromEnvUsesDefaultsWhenOnlyPrimarySet(t *testing.T) {
	const prefix = "TESTSVC"
	t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy?sslmode=disable")

	cfg, err := LoadFromEnv(prefix)
	if err != nil {
		t.Fatalf("load from env: %v", err)
	}
	if cfg.PrimaryDSN != "postgres://example/galaxy?sslmode=disable" {
		t.Fatalf("primary DSN = %q", cfg.PrimaryDSN)
	}
	if len(cfg.ReplicaDSNs) != 0 {
		t.Fatalf("replica DSNs = %v, want empty", cfg.ReplicaDSNs)
	}
	if cfg.OperationTimeout != DefaultOperationTimeout {
		t.Fatalf("operation timeout = %v", cfg.OperationTimeout)
	}
	if cfg.MaxOpenConns != DefaultMaxOpenConns {
		t.Fatalf("max open conns = %d", cfg.MaxOpenConns)
	}
}

func TestLoadFromEnvParsesAllOverrides(t *testing.T) {
	const prefix = "TESTSVC"
	t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy?sslmode=disable")
	t.Setenv(prefix+"_POSTGRES_REPLICA_DSNS", "postgres://r1, postgres://r2 ,")
	t.Setenv(prefix+"_POSTGRES_OPERATION_TIMEOUT", "750ms")
	t.Setenv(prefix+"_POSTGRES_MAX_OPEN_CONNS", "40")
	t.Setenv(prefix+"_POSTGRES_MAX_IDLE_CONNS", "10")
	t.Setenv(prefix+"_POSTGRES_CONN_MAX_LIFETIME", "15m")

	cfg, err := LoadFromEnv(prefix)
	if err != nil {
		t.Fatalf("load from env: %v", err)
	}
	if got, want := cfg.OperationTimeout, 750*time.Millisecond; got != want {
		t.Fatalf("operation timeout = %v, want %v", got, want)
	}
	if got, want := cfg.MaxOpenConns, 40; got != want {
		t.Fatalf("max open conns = %d, want %d", got, want)
	}
	if got, want := cfg.MaxIdleConns, 10; got != want {
		t.Fatalf("max idle conns = %d, want %d", got, want)
	}
	if got, want := cfg.ConnMaxLifetime, 15*time.Minute; got != want {
		t.Fatalf("conn max lifetime = %v, want %v", got, want)
	}
	if got, want := len(cfg.ReplicaDSNs), 2; got != want {
		t.Fatalf("replica DSN count = %d, want %d", got, want)
	}
	if cfg.ReplicaDSNs[0] != "postgres://r1" || cfg.ReplicaDSNs[1] != "postgres://r2" {
		t.Fatalf("replica DSNs = %v", cfg.ReplicaDSNs)
	}
}

func TestLoadFromEnvFailsWhenPrimaryMissing(t *testing.T) {
	const prefix = "TESTSVC"
	t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "")

	if _, err := LoadFromEnv(prefix); err == nil {
		t.Fatal("expected error when primary DSN missing")
	}
}

func TestLoadFromEnvRejectsEmptyPrefix(t *testing.T) {
	t.Parallel()

	if _, err := LoadFromEnv("   "); err == nil {
		t.Fatal("expected error on empty prefix")
	}
}

func TestLoadFromEnvSurfacesDurationParseErrors(t *testing.T) {
	const prefix = "TESTSVC"
	t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy")
	t.Setenv(prefix+"_POSTGRES_OPERATION_TIMEOUT", "not-a-duration")

	if _, err := LoadFromEnv(prefix); err == nil {
		t.Fatal("expected parse error")
	} else if !strings.Contains(err.Error(), "OPERATION_TIMEOUT") {
		t.Fatalf("error %q should name the env var", err)
	}
}
