package config

import (
	"os"
	"testing"

	"gopkg.in/yaml.v3"
)

// --- IntRange ---

func TestIntRangeValue_Scalar(t *testing.T) {
	r := IntRange{Min: 5, Max: 5}
	if v := r.Value(); v != 5 {
		t.Errorf("expected 5, got %d", v)
	}
}

func TestIntRangeValue_Range(t *testing.T) {
	r := IntRange{Min: 10, Max: 20}
	for i := 0; i < 100; i++ {
		v := r.Value()
		if v < 10 || v > 20 {
			t.Errorf("value %d out of [10, 20]", v)
		}
	}
}

func TestIntRangeUnmarshalYAML_Scalar(t *testing.T) {
	yamlData := []byte("timeout: 5000\n")
	type wrapper struct {
		Timeout int `yaml:"timeout"`
	}
	var w wrapper
	if err := yaml.Unmarshal(yamlData, &w); err != nil {
		t.Fatal(err)
	}
	if w.Timeout != 5000 {
		t.Errorf("expected 5000, got %d", w.Timeout)
	}
}

func TestIntRangeUnmarshalYAML_FromFullConfig(t *testing.T) {
	yamlData := []byte(`
check:
  playlists:
    allCooldown: 1
    oneCooldown: [2, 3]
  channels:
    cooldown: [0, 1]
`)
	cfg := defaults()
	if err := yaml.Unmarshal(yamlData, cfg); err != nil {
		t.Fatal(err)
	}
	if cfg.Check.Playlists.AllCooldown.Min != 1000 || cfg.Check.Playlists.AllCooldown.Max != 1000 {
		t.Errorf("scalar allCooldown: expected Min=Max=1000 ms from 1 s, got Min=%d Max=%d",
			cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
	}
	if cfg.Check.Playlists.OneCooldown.Min != 2000 || cfg.Check.Playlists.OneCooldown.Max != 3000 {
		t.Errorf("range oneCooldown: expected [2000,3000] ms from [2,3] s, got [%d,%d]",
			cfg.Check.Playlists.OneCooldown.Min, cfg.Check.Playlists.OneCooldown.Max)
	}
	if cfg.Check.Channels.Cooldown.Min != 0 || cfg.Check.Channels.Cooldown.Max != 1000 {
		t.Errorf("range cooldown: expected [0,1000] ms from [0,1] s, got [%d,%d]",
			cfg.Check.Channels.Cooldown.Min, cfg.Check.Channels.Cooldown.Max)
	}
}

// --- UserAgents ---

func TestUserAgentsPick_Empty(t *testing.T) {
	ua := UserAgents{}
	if ua.Pick() != "" {
		t.Error("expected empty string for empty UserAgents")
	}
}

func TestUserAgentsPick_Single(t *testing.T) {
	ua := UserAgents{"Mozilla"}
	if ua.Pick() != "Mozilla" {
		t.Errorf("expected 'Mozilla', got '%s'", ua.Pick())
	}
}

func TestUserAgentsPick_Multiple(t *testing.T) {
	ua := UserAgents{"A", "B", "C"}
	for i := 0; i < 100; i++ {
		v := ua.Pick()
		found := false
		for _, s := range ua {
			if v == s {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("Pick returned '%s' which is not in list", v)
		}
	}
}

func TestUserAgentsUnmarshalYAML_Scalar(t *testing.T) {
	yamlData := []byte(`
check:
  channels:
    userAgent: Mozilla/5.0
`)
	cfg := defaults()
	if err := yaml.Unmarshal(yamlData, cfg); err != nil {
		t.Fatal(err)
	}
	if len(cfg.Check.Channels.UserAgent) != 1 || cfg.Check.Channels.UserAgent[0] != "Mozilla/5.0" {
		t.Errorf("expected single 'Mozilla/5.0', got %v", cfg.Check.Channels.UserAgent)
	}
}

func TestUserAgentsUnmarshalYAML_Array(t *testing.T) {
	yamlData := []byte(`
check:
  channels:
    userAgent:
      - Mozilla/5.0
      - curl/8.0
`)
	cfg := defaults()
	if err := yaml.Unmarshal(yamlData, cfg); err != nil {
		t.Fatal(err)
	}
	if len(cfg.Check.Channels.UserAgent) != 2 {
		t.Errorf("expected 2 userAgents, got %d", len(cfg.Check.Channels.UserAgent))
	}
}

// --- parseBool / parseUint / parseInt ---

func TestParseBool(t *testing.T) {
	cases := []struct {
		in   string
		def  bool
		want bool
	}{
		{"true", false, true},
		{"1", false, true},
		{"false", true, false},
		{"0", true, false},
		{"yes", false, false},
		{"", true, true},
	}
	for _, c := range cases {
		got := parseBool(c.in, c.def)
		if got != c.want {
			t.Errorf("parseBool(%q, %v) = %v, want %v", c.in, c.def, got, c.want)
		}
	}
}

func TestParseUint(t *testing.T) {
	cases := []struct {
		in   string
		def  uint
		want uint
	}{
		{"8800", 0, 8800},
		{"0", 999, 0},
		{"abc", 999, 999},
		{"", 999, 999},
	}
	for _, c := range cases {
		got := parseUint(c.in, c.def)
		if got != c.want {
			t.Errorf("parseUint(%q, %d) = %d, want %d", c.in, c.def, got, c.want)
		}
	}
}

func TestParseInt(t *testing.T) {
	cases := []struct {
		in   string
		def  int
		want int
	}{
		{"100", 0, 100},
		{"-5", 0, -5},
		{"abc", 999, 999},
		{"", 999, 999},
	}
	for _, c := range cases {
		got := parseInt(c.in, c.def)
		if got != c.want {
			t.Errorf("parseInt(%q, %d) = %d, want %d", c.in, c.def, got, c.want)
		}
	}
}

// --- applyEnvIntRange ---

func TestApplyEnvIntRange_Scalar(t *testing.T) {
	t.Setenv("TEST_COOLDOWN", "1.5")
	r := IntRange{Min: 0, Max: 0}
	applyEnvIntRange(&r, "TEST_COOLDOWN")
	if r.Min != 1500 || r.Max != 1500 {
		t.Errorf("expected Min=Max=1500, got Min=%d Max=%d", r.Min, r.Max)
	}
}

func TestApplyEnvIntRange_Range(t *testing.T) {
	t.Setenv("TEST_COOLDOWN", "1,2")
	r := IntRange{Min: 0, Max: 0}
	applyEnvIntRange(&r, "TEST_COOLDOWN")
	if r.Min != 1000 || r.Max != 2000 {
		t.Errorf("expected [1000,2000], got [%d,%d]", r.Min, r.Max)
	}
}

func TestApplyEnvIntRange_Invalid(t *testing.T) {
	t.Setenv("TEST_COOLDOWN", "1,invalid")
	r := IntRange{Min: 5, Max: 5}
	applyEnvIntRange(&r, "TEST_COOLDOWN")
	if r.Min != 5 || r.Max != 5 {
		t.Errorf("expected unchanged [5,5], got [%d,%d]", r.Min, r.Max)
	}
}

func TestApplyEnvIntRange_None(t *testing.T) {
	r := IntRange{Min: 7, Max: 7}
	applyEnvIntRange(&r, "TEST_COOLDOWN")
	if r.Min != 7 || r.Max != 7 {
		t.Errorf("expected unchanged [7,7], got [%d,%d]", r.Min, r.Max)
	}
}

// --- applyEnvStringSlice ---

func TestApplyEnvStringSlice_Multiple(t *testing.T) {
	t.Setenv("MY_PREFIX_1", "foo")
	t.Setenv("MY_PREFIX_2", "bar")
	t.Setenv("MY_PREFIX_3", "baz")
	result := applyEnvStringSlice("MY_PREFIX")
	if len(result) != 3 {
		t.Fatalf("expected 3 items, got %d", len(result))
	}
	if result[0] != "foo" || result[1] != "bar" || result[2] != "baz" {
		t.Errorf("unexpected result: %v", result)
	}
}

func TestApplyEnvStringSlice_EmptyValue(t *testing.T) {
	t.Setenv("MY_PREFIX_1", "foo")
	t.Setenv("MY_PREFIX_2", "")
	t.Setenv("MY_PREFIX_3", "baz")
	result := applyEnvStringSlice("MY_PREFIX")
	if len(result) != 2 {
		t.Fatalf("expected 2 items (empty skipped), got %d: %v", len(result), result)
	}
	if result[0] != "foo" || result[1] != "baz" {
		t.Errorf("unexpected result: %v", result)
	}
}

func TestApplyEnvStringSlice_None(t *testing.T) {
	result := applyEnvStringSlice("NONEXISTENT_PREFIX")
	if result != nil {
		t.Errorf("expected nil, got %v", result)
	}
}

// --- validate ---

func TestValidate_ServerPort(t *testing.T) {
	cfg := defaults()
	cfg.Server.Port = 0
	validate(cfg)
	if cfg.Server.Port != 8800 {
		t.Errorf("expected 8800, got %d", cfg.Server.Port)
	}
}

func TestValidate_ServerPortTooHigh(t *testing.T) {
	cfg := defaults()
	cfg.Server.Port = 99999
	validate(cfg)
	if cfg.Server.Port != 8800 {
		t.Errorf("expected 8800, got %d", cfg.Server.Port)
	}
}

func TestValidate_BaseUrlEmpty(t *testing.T) {
	cfg := defaults()
	cfg.Site.BaseUrl = ""
	cfg.Server.Port = 3000
	validate(cfg)
	if cfg.Site.BaseUrl != "http://localhost:3000" {
		t.Errorf("expected http://localhost:3000, got %s", cfg.Site.BaseUrl)
	}
}

func TestValidate_CacheEnabled(t *testing.T) {
	cfg := defaults()
	cfg.Cache.Enabled = true
	cfg.Cache.Host = ""
	cfg.Cache.Port = 0
	cfg.Cache.Ttl = 0
	validate(cfg)
	if cfg.Cache.Host != "cache" {
		t.Errorf("expected cache, got %s", cfg.Cache.Host)
	}
	if cfg.Cache.Port != 6379 {
		t.Errorf("expected 6379, got %d", cfg.Cache.Port)
	}
	if cfg.Cache.Ttl != 3600 {
		t.Errorf("expected 3600, got %d", cfg.Cache.Ttl)
	}
}

func TestValidate_PlaylistsTimeout(t *testing.T) {
	cfg := defaults()
	cfg.Check.Playlists.Timeout = 0
	validate(cfg)
	if cfg.Check.Playlists.Timeout != 10000 {
		t.Errorf("expected 10000, got %d", cfg.Check.Playlists.Timeout)
	}
}

func TestValidate_ChannelsTimeout(t *testing.T) {
	cfg := defaults()
	cfg.Check.Channels.Timeout = -5
	validate(cfg)
	if cfg.Check.Channels.Timeout != 10000 {
		t.Errorf("expected 10000, got %d", cfg.Check.Channels.Timeout)
	}
}

func TestValidate_PlaylistsMaxRoutines(t *testing.T) {
	cfg := defaults()
	cfg.Check.Playlists.MaxRoutines = 0
	validate(cfg)
	if cfg.Check.Playlists.MaxRoutines != 1 {
		t.Errorf("expected 1, got %d", cfg.Check.Playlists.MaxRoutines)
	}
}

func TestValidate_ChannelsMaxRoutines(t *testing.T) {
	cfg := defaults()
	cfg.Check.Channels.MaxRoutines = 0
	validate(cfg)
	if cfg.Check.Channels.MaxRoutines != 50 {
		t.Errorf("expected 50, got %d", cfg.Check.Channels.MaxRoutines)
	}
}

func TestValidate_ChannelsByteRange(t *testing.T) {
	cfg := defaults()
	cfg.Check.Channels.ByteRange = 0
	validate(cfg)
	if cfg.Check.Channels.ByteRange != 512 {
		t.Errorf("expected 512, got %d", cfg.Check.Channels.ByteRange)
	}
}

func TestValidate_UserAgentEmpty(t *testing.T) {
	cfg := defaults()
	cfg.Check.Playlists.UserAgent = nil
	cfg.Check.Channels.UserAgent = nil
	validate(cfg)
	if len(cfg.Check.Playlists.UserAgent) == 0 {
		t.Error("expected non-empty playlists userAgent")
	}
	if len(cfg.Check.Channels.UserAgent) == 0 {
		t.Error("expected non-empty channels userAgent")
	}
}

// --- validateIntRange ---

func TestValidateIntRange_Swap(t *testing.T) {
	r := IntRange{Min: 100, Max: 10}
	validateIntRange(&r, "test", 0, 1000)
	if r.Min != 10 || r.Max != 100 {
		t.Errorf("expected [10,100] after swap, got [%d,%d]", r.Min, r.Max)
	}
}

func TestValidateIntRange_ClampMin(t *testing.T) {
	r := IntRange{Min: -5, Max: 10}
	validateIntRange(&r, "test", 0, 1000)
	if r.Min != 0 {
		t.Errorf("expected Min=0 after clamp, got %d", r.Min)
	}
}

func TestValidateIntRange_ClampMax(t *testing.T) {
	r := IntRange{Min: 0, Max: 999999}
	validateIntRange(&r, "test", 0, 1000)
	if r.Max != 1000 {
		t.Errorf("expected Max=1000 after clamp, got %d", r.Max)
	}
}

func TestValidateIntRange_SwapThenClamp(t *testing.T) {
	r := IntRange{Min: 2000, Max: -10}
	validateIntRange(&r, "test", 0, 1000)
	// swap → [−10, 2000], clamp → [0, 1000]
	if r.Min != 0 || r.Max != 1000 {
		t.Errorf("expected [0,1000], got [%d,%d]", r.Min, r.Max)
	}
}

// --- defaults ---

func TestDefaults(t *testing.T) {
	cfg := defaults()
	if cfg.App.Timezone != "GMT" {
		t.Errorf("expected GMT, got %s", cfg.App.Timezone)
	}
	if cfg.Server.Port != 8800 {
		t.Errorf("expected 8800, got %d", cfg.Server.Port)
	}
	if cfg.Check.Playlists.Timeout != 10000 {
		t.Errorf("expected 10000 ms, got %d", cfg.Check.Playlists.Timeout)
	}
	if cfg.Check.Channels.Timeout != 10000 {
		t.Errorf("expected 10000 ms, got %d", cfg.Check.Channels.Timeout)
	}
	if cfg.Cache.Port != 6379 {
		t.Errorf("expected 6379, got %d", cfg.Cache.Port)
	}
}

// --- Init (integration) ---

func TestInit_NoConfigFile(t *testing.T) {
	cfg := Init("/nonexistent/config.yml")
	if cfg == nil {
		t.Fatal("expected non-nil config")
	}
	if cfg.Server.Port != 8800 {
		t.Errorf("expected default port 8800, got %d", cfg.Server.Port)
	}
}

func TestInit_WithConfigFile(t *testing.T) {
	tmp := t.TempDir() + "/config.yml"
	data := []byte(`
app:
  timezone: UTC+3
  debug: true
server:
  port: 3000
  host: 0.0.0.0
check:
  playlists:
    timeout: 5
    allCooldown: [1, 2]
  channels:
    byteRange: 1024
cache:
  enabled: true
  host: db.local
  port: 6380
`)
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		t.Fatal(err)
	}
	cfg := Init(tmp)
	if cfg.App.Timezone != "UTC+3" {
		t.Errorf("expected UTC+3, got %s", cfg.App.Timezone)
	}
	if !cfg.App.Debug {
		t.Error("expected debug=true")
	}
	if cfg.Server.Port != 3000 {
		t.Errorf("expected 3000, got %d", cfg.Server.Port)
	}
	if cfg.Server.Host != "0.0.0.0" {
		t.Errorf("expected 0.0.0.0, got %s", cfg.Server.Host)
	}
	if cfg.Check.Playlists.Timeout != 5000 {
		t.Errorf("expected 5000 ms from 5 s, got %d", cfg.Check.Playlists.Timeout)
	}
	if cfg.Check.Playlists.AllCooldown.Min != 1000 || cfg.Check.Playlists.AllCooldown.Max != 2000 {
		t.Errorf("expected [1000,2000] ms from [1,2] s, got [%d,%d]",
			cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
	}
	if cfg.Check.Channels.ByteRange != 1024 {
		t.Errorf("expected 1024, got %d", cfg.Check.Channels.ByteRange)
	}
	if !cfg.Cache.Enabled {
		t.Error("expected cache enabled")
	}
	if cfg.Cache.Host != "db.local" {
		t.Errorf("expected db.local, got %s", cfg.Cache.Host)
	}
	if cfg.Cache.Port != 6380 {
		t.Errorf("expected 6380, got %d", cfg.Cache.Port)
	}
}

func TestInit_EnvOverridesConfig(t *testing.T) {
	tmp := t.TempDir() + "/config.yml"
	data := []byte("server:\n  port: 3000\n")
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		t.Fatal(err)
	}
	t.Setenv("SERVER_PORT", "9090")
	t.Setenv("APP_DEBUG", "true")
	t.Setenv("CHECK_PLAYLISTS_TIMEOUT", "7")
	t.Setenv("CHECK_PLAYLISTS_USER_AGENT_1", "CustomUA")
	t.Setenv("CHECK_PLAYLISTS_ALL_COOLDOWN", "1.5,2.5")

	cfg := Init(tmp)
	if cfg.Server.Port != 9090 {
		t.Errorf("expected 9090 from env, got %d", cfg.Server.Port)
	}
	if !cfg.App.Debug {
		t.Error("expected debug=true from env")
	}
	if cfg.Check.Playlists.Timeout != 7000 {
		t.Errorf("expected 7000 ms from 7 s env, got %d", cfg.Check.Playlists.Timeout)
	}
	if len(cfg.Check.Playlists.UserAgent) != 1 || cfg.Check.Playlists.UserAgent[0] != "CustomUA" {
		t.Errorf("expected [CustomUA], got %v", cfg.Check.Playlists.UserAgent)
	}
	if cfg.Check.Playlists.AllCooldown.Min != 1500 || cfg.Check.Playlists.AllCooldown.Max != 2500 {
		t.Errorf("expected [1500,2500] ms from [1.5,2.5] s env, got [%d,%d]",
			cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
	}
}

func TestInit_EnvScalarCooldownOverridesRange(t *testing.T) {
	tmp := t.TempDir() + "/config.yml"
	data := []byte("check:\n  playlists:\n    allCooldown: [1, 2]\n")
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		t.Fatal(err)
	}
	t.Setenv("CHECK_PLAYLISTS_ALL_COOLDOWN", "2.5")

	cfg := Init(tmp)
	if cfg.Check.Playlists.AllCooldown.Min != 2500 || cfg.Check.Playlists.AllCooldown.Max != 2500 {
		t.Errorf("expected scalar [2500,2500] ms from 2.5 s env, got [%d,%d]",
			cfg.Check.Playlists.AllCooldown.Min, cfg.Check.Playlists.AllCooldown.Max)
	}
}

// helper removed — using yaml.Unmarshal directly
