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"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	"github.com/treytartt/honeydue-api/internal/models"
)

// setupTestDB creates a temporary in-memory SQLite database with the required
// tables for auth middleware tests.
func setupTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	require.NoError(t, err)

	err = db.AutoMigrate(&models.User{}, &models.AuthToken{})
	require.NoError(t, err)

	return db
}

// createTestUserAndToken creates a user and an auth token, then backdates the
// token's Created timestamp by the specified number of days.
func createTestUserAndToken(t *testing.T, db *gorm.DB, username string, ageDays int) (*models.User, *models.AuthToken) {
	t.Helper()

	user := &models.User{
		Username: username,
		Email:    username + "@test.com",
		IsActive: true,
	}
	require.NoError(t, user.SetPassword("Password123"))
	require.NoError(t, db.Create(user).Error)

	token := &models.AuthToken{
		UserID: user.ID,
	}
	require.NoError(t, db.Create(token).Error)

	// Backdate the token's Created timestamp after creation to bypass autoCreateTime
	backdated := time.Now().UTC().AddDate(0, 0, -ageDays)
	require.NoError(t, db.Model(token).Update("created", backdated).Error)
	token.Created = backdated

	return user, token
}

func TestTokenAuth_RejectsExpiredToken(t *testing.T) {
	db := setupTestDB(t)
	_, token := createTestUserAndToken(t, db, "expired_user", 91) // 91 days old > 90 day expiry

	m := NewAuthMiddleware(db, nil) // No Redis cache for these tests

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set("Authorization", "Token "+token.Key)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	handler := m.TokenAuth()(func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	err := handler(c)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "error.token_expired")
}

func TestTokenAuth_AcceptsValidToken(t *testing.T) {
	db := setupTestDB(t)
	_, token := createTestUserAndToken(t, db, "valid_user", 30) // 30 days old < 90 day expiry

	m := NewAuthMiddleware(db, nil)

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set("Authorization", "Token "+token.Key)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	handler := m.TokenAuth()(func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, http.StatusOK, rec.Code)

	// Verify user was set in context
	user := GetAuthUser(c)
	require.NotNil(t, user)
	assert.Equal(t, "valid_user", user.Username)
}

func TestTokenAuth_AcceptsTokenAtBoundary(t *testing.T) {
	db := setupTestDB(t)
	_, token := createTestUserAndToken(t, db, "boundary_user", 89) // 89 days old, just under 90 day expiry

	m := NewAuthMiddleware(db, nil)

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set("Authorization", "Token "+token.Key)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	handler := m.TokenAuth()(func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	err := handler(c)
	require.NoError(t, err)
	assert.Equal(t, http.StatusOK, rec.Code)
}

func TestTokenAuth_RejectsInvalidToken(t *testing.T) {
	db := setupTestDB(t)

	m := NewAuthMiddleware(db, nil)

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	req.Header.Set("Authorization", "Token nonexistent-token")
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	handler := m.TokenAuth()(func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	err := handler(c)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "error.invalid_token")
}

func TestTokenAuth_RejectsNoAuthHeader(t *testing.T) {
	db := setupTestDB(t)

	m := NewAuthMiddleware(db, nil)

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/test/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	handler := m.TokenAuth()(func(c echo.Context) error {
		return c.String(http.StatusOK, "ok")
	})

	err := handler(c)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "error.not_authenticated")
}
