package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestTimezoneMiddleware_IANATimezone(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()
	handler := mw(func(c echo.Context) error {
		loc := GetUserTimezone(c)
		return c.String(http.StatusOK, loc.String())
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set(TimezoneHeader, "America/New_York")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, "America/New_York", rec.Body.String())
}

func TestTimezoneMiddleware_UTCOffset(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()
	handler := mw(func(c echo.Context) error {
		loc := GetUserTimezone(c)
		// Just verify it's not UTC (if offset is non-zero)
		return c.String(http.StatusOK, loc.String())
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set(TimezoneHeader, "-05:00")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, "-05:00", rec.Body.String())
}

func TestTimezoneMiddleware_NoHeader_DefaultsToUTC(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()
	handler := mw(func(c echo.Context) error {
		loc := GetUserTimezone(c)
		return c.String(http.StatusOK, loc.String())
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	// No X-Timezone header
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, "UTC", rec.Body.String())
}

func TestTimezoneMiddleware_InvalidTimezone_DefaultsToUTC(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()
	handler := mw(func(c echo.Context) error {
		loc := GetUserTimezone(c)
		return c.String(http.StatusOK, loc.String())
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set(TimezoneHeader, "Invalid/Timezone")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, "UTC", rec.Body.String())
}

func TestTimezoneMiddleware_SetsUserNow(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()

	var capturedNow time.Time
	handler := mw(func(c echo.Context) error {
		capturedNow = GetUserNow(c)
		return c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set(TimezoneHeader, "America/Chicago")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)

	// The "now" should be the start of the day in the user's timezone
	assert.Equal(t, 0, capturedNow.Hour())
	assert.Equal(t, 0, capturedNow.Minute())
	assert.Equal(t, 0, capturedNow.Second())
}

func TestTimezoneMiddleware_SetsTimezoneName(t *testing.T) {
	mw := TimezoneMiddleware()
	e := echo.New()

	handler := mw(func(c echo.Context) error {
		name := GetTimezoneName(c)
		return c.String(http.StatusOK, name)
	})

	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set(TimezoneHeader, "Europe/London")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, "Europe/London", rec.Body.String())
}

func TestGetUserTimezone_NotSet_ReturnsUTC(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// No timezone set in context
	loc := GetUserTimezone(c)
	assert.Equal(t, time.UTC, loc)
}

func TestGetUserTimezone_WrongType_ReturnsUTC(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	c.Set(TimezoneKey, "not-a-location")
	loc := GetUserTimezone(c)
	assert.Equal(t, time.UTC, loc)
}

func TestGetUserNow_NotSet_ReturnsUTCNow(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	before := time.Now().UTC()
	now := GetUserNow(c)
	after := time.Now().UTC()

	assert.True(t, !now.Before(before.Add(-time.Second)), "now should be roughly after before")
	assert.True(t, !now.After(after.Add(time.Second)), "now should be roughly before after")
}

func TestGetUserNow_WrongType_ReturnsUTCNow(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	c.Set(UserNowKey, "not-a-time")
	now := GetUserNow(c)
	assert.NotNil(t, now)
}

func TestIsTimezoneChanged_NoChange_ReturnsFalse(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	c.Set(TimezoneChangedKey, false)
	assert.False(t, IsTimezoneChanged(c))
}

func TestIsTimezoneChanged_Changed_ReturnsTrue(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	c.Set(TimezoneChangedKey, true)
	assert.True(t, IsTimezoneChanged(c))
}

func TestIsTimezoneChanged_NotSet_ReturnsFalse(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// Not set at all
	assert.False(t, IsTimezoneChanged(c))
}

func TestParseTimezone_UTCOffsetWithoutColon(t *testing.T) {
	loc := parseTimezone("-0800")
	assert.NotEqual(t, time.UTC, loc)
	assert.Equal(t, "-0800", loc.String())
}

func TestParseTimezone_PositiveOffset(t *testing.T) {
	loc := parseTimezone("+05:30")
	assert.NotEqual(t, time.UTC, loc)
	assert.Equal(t, "+05:30", loc.String())
}

func TestParseTimezone_UTC(t *testing.T) {
	loc := parseTimezone("UTC")
	assert.Equal(t, time.UTC, loc)
}
