package timezone

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestInit(t *testing.T) {
	// Save original state
	originalLoc := location
	originalTzName := tzName
	defer func() {
		location = originalLoc
		tzName = originalTzName
	}()

	tests := []struct {
		name      string
		tz        string
		wantErr   bool
		wantName  string
	}{
		{
			name:     "valid timezone Asia/Shanghai",
			tz:       "Asia/Shanghai",
			wantErr:  false,
			wantName: "Asia/Shanghai",
		},
		{
			name:     "valid timezone UTC",
			tz:       "UTC",
			wantErr:  false,
			wantName: "UTC",
		},
		{
			name:     "empty string uses default",
			tz:       "",
			wantErr:  false,
			wantName: "Asia/Shanghai",
		},
		{
			name:     "invalid timezone",
			tz:       "Invalid/Timezone",
			wantErr:  true,
			wantName: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Reset state
			location = nil
			tzName = ""

			err := Init(tt.tz)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.wantName, Name())
			}
		})
	}
}

func TestGetUTCOffset(t *testing.T) {
	loc, _ := time.LoadLocation("Asia/Shanghai")
	offset := getUTCOffset(loc)
	assert.NotEmpty(t, offset)
	// Should be +08:00 for Shanghai
	assert.Contains(t, offset, "+")
}

func TestNow(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	t.Run("with location set", func(t *testing.T) {
		loc, _ := time.LoadLocation("Asia/Shanghai")
		location = loc
		now := Now()
		assert.NotZero(t, now)
	})

	t.Run("without location", func(t *testing.T) {
		location = nil
		now := Now()
		assert.NotZero(t, now)
	})
}

func TestLocation(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	t.Run("with location set", func(t *testing.T) {
		loc, _ := time.LoadLocation("Asia/Shanghai")
		location = loc
		assert.Equal(t, loc, Location())
	})

	t.Run("without location", func(t *testing.T) {
		location = nil
		assert.Equal(t, time.Local, Location())
	})
}

func TestName(t *testing.T) {
	// Save and restore
	originalName := tzName
	defer func() { tzName = originalName }()

	t.Run("with name set", func(t *testing.T) {
		tzName = "Asia/Shanghai"
		assert.Equal(t, "Asia/Shanghai", Name())
	})

	t.Run("without name", func(t *testing.T) {
		tzName = ""
		assert.Equal(t, "Local", Name())
	})
}

func TestStartOfDay(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	now := time.Date(2024, 6, 15, 14, 30, 45, 0, loc)
	start := StartOfDay(now)

	assert.Equal(t, 2024, start.Year())
	assert.Equal(t, time.Month(6), start.Month())
	assert.Equal(t, 15, start.Day())
	assert.Equal(t, 0, start.Hour())
	assert.Equal(t, 0, start.Minute())
	assert.Equal(t, 0, start.Second())
}

func TestToday(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	today := Today()
	assert.Equal(t, 0, today.Hour())
	assert.Equal(t, 0, today.Minute())
	assert.Equal(t, 0, today.Second())
}

func TestEndOfDay(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	now := time.Date(2024, 6, 15, 14, 30, 45, 0, loc)
	end := EndOfDay(now)

	assert.Equal(t, 2024, end.Year())
	assert.Equal(t, time.Month(6), end.Month())
	assert.Equal(t, 15, end.Day())
	assert.Equal(t, 23, end.Hour())
	assert.Equal(t, 59, end.Minute())
	assert.Equal(t, 59, end.Second())
}

func TestStartOfWeek(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	// Friday June 14, 2024
	friday := time.Date(2024, 6, 14, 10, 0, 0, 0, loc)
	start := StartOfWeek(friday)

	// Should be Monday June 10, 2024
	assert.Equal(t, 2024, start.Year())
	assert.Equal(t, time.Month(6), start.Month())
	assert.Equal(t, 10, start.Day())
	assert.Equal(t, time.Monday, start.Weekday())
}

func TestStartOfMonth(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	midMonth := time.Date(2024, 6, 15, 10, 0, 0, 0, loc)
	start := StartOfMonth(midMonth)

	assert.Equal(t, 2024, start.Year())
	assert.Equal(t, time.Month(6), start.Month())
	assert.Equal(t, 1, start.Day())
}

func TestParseInLocation(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	parsed, err := ParseInLocation("2006-01-02", "2024-06-15")
	require.NoError(t, err)
	assert.Equal(t, 2024, parsed.Year())
	assert.Equal(t, time.Month(6), parsed.Month())
	assert.Equal(t, 15, parsed.Day())
}

func TestParseInUserLocation(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	tests := []struct {
		name   string
		userTZ string
	}{
		{"with valid user timezone", "America/New_York"},
		{"with empty user timezone", ""},
		{"with invalid user timezone", "Invalid/Zone"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parsed, err := ParseInUserLocation("2006-01-02", "2024-06-15", tt.userTZ)
			require.NoError(t, err)
			assert.Equal(t, 2024, parsed.Year())
			assert.Equal(t, time.Month(6), parsed.Month())
		})
	}
}

func TestNowInUserLocation(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	tests := []struct {
		name   string
		userTZ string
	}{
		{"with valid user timezone", "America/New_York"},
		{"with empty user timezone", ""},
		{"with invalid user timezone", "Invalid/Zone"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			now := NowInUserLocation(tt.userTZ)
			assert.NotZero(t, now)
		})
	}
}

func TestStartOfDayInUserLocation(t *testing.T) {
	// Save and restore
	originalLoc := location
	defer func() { location = originalLoc }()

	loc, _ := time.LoadLocation("Asia/Shanghai")
	location = loc

	now := time.Date(2024, 6, 15, 14, 30, 0, 0, loc)

	tests := []struct {
		name   string
		userTZ string
	}{
		{"with valid user timezone", "America/New_York"},
		{"with empty user timezone", ""},
		{"with invalid user timezone", "Invalid/Zone"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			start := StartOfDayInUserLocation(now, tt.userTZ)
			assert.Equal(t, 0, start.Hour())
			assert.Equal(t, 0, start.Minute())
		})
	}
}
