package handlers

import (
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

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

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

func setupNotificationHandler(t *testing.T) (*NotificationHandler, *echo.Echo, *gorm.DB) {
	db := testutil.SetupTestDB(t)
	notifRepo := repositories.NewNotificationRepository(db)
	notifService := services.NewNotificationService(notifRepo, nil)
	handler := NewNotificationHandler(notifService)
	e := testutil.SetupTestRouter()
	return handler, e, db
}

func createTestNotifications(t *testing.T, db *gorm.DB, userID uint, count int) {
	for i := 0; i < count; i++ {
		notif := &models.Notification{
			UserID:           userID,
			NotificationType: models.NotificationTaskDueSoon,
			Title:            fmt.Sprintf("Test Notification %d", i+1),
			Body:             fmt.Sprintf("Body %d", i+1),
		}
		err := db.Create(notif).Error
		require.NoError(t, err)
	}
}

func TestNotificationHandler_ListNotifications_LimitCappedAt200(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	// Create 210 notifications to exceed the cap
	createTestNotifications(t, db, user.ID, 210)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.ListNotifications)

	t.Run("limit is capped at 200 when user requests more", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=999", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		count := int(response["count"].(float64))
		assert.Equal(t, 200, count, "response should contain at most 200 notifications when limit exceeds cap")
	})

	t.Run("limit below cap is respected", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=10", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		count := int(response["count"].(float64))
		assert.Equal(t, 10, count, "response should respect limit when below cap")
	})

	t.Run("default limit is used when no limit param", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		count := int(response["count"].(float64))
		assert.Equal(t, 50, count, "response should use default limit of 50")
	})
}

func TestNotificationHandler_ListNotifications_Pagination(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	createTestNotifications(t, db, user.ID, 20)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/", handler.ListNotifications)

	t.Run("offset skips notifications", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=5&offset=15", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		count := int(response["count"].(float64))
		assert.Equal(t, 5, count, "should return remaining 5 after offset 15")
	})

	t.Run("response has results array", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=3", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Contains(t, response, "results")
		assert.Contains(t, response, "count")
		results := response["results"].([]interface{})
		assert.Len(t, results, 3)
	})

	t.Run("negative limit ignored", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=-5", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		// Negative limit should default to 50 (since -5 > 0 is false)
		count := int(response["count"].(float64))
		assert.Equal(t, 20, count, "should return all 20 with default limit of 50")
	})
}

func TestNotificationHandler_GetUnreadCount(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	// Create some unread notifications
	createTestNotifications(t, db, user.ID, 5)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/unread-count/", handler.GetUnreadCount)

	t.Run("successful unread count", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/unread-count/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Contains(t, response, "unread_count")
		unreadCount := int(response["unread_count"].(float64))
		assert.Equal(t, 5, unreadCount)
	})

	t.Run("user with no notifications returns zero", func(t *testing.T) {
		otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")

		e2 := testutil.SetupTestRouter()
		authGroup2 := e2.Group("/api/notifications")
		authGroup2.Use(testutil.MockAuthMiddleware(otherUser))
		authGroup2.GET("/unread-count/", handler.GetUnreadCount)

		w := testutil.MakeRequest(e2, "GET", "/api/notifications/unread-count/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, float64(0), response["unread_count"])
	})
}

func TestNotificationHandler_MarkAsRead(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	// Create a notification
	notif := &models.Notification{
		UserID:           user.ID,
		NotificationType: models.NotificationTaskDueSoon,
		Title:            "Test Notification",
		Body:             "Test Body",
	}
	require.NoError(t, db.Create(notif).Error)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/:id/read/", handler.MarkAsRead)

	t.Run("successful mark as read", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/notifications/%d/read/", notif.ID), nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "message")
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/notifications/invalid/read/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("not found returns 404", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/notifications/99999/read/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusNotFound)
	})
}

func TestNotificationHandler_MarkAllAsRead(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	createTestNotifications(t, db, user.ID, 5)

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/mark-all-read/", handler.MarkAllAsRead)
	authGroup.GET("/unread-count/", handler.GetUnreadCount)

	t.Run("successful mark all as read", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/notifications/mark-all-read/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Contains(t, response, "message")
	})

	t.Run("unread count is zero after mark all", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/unread-count/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, float64(0), response["unread_count"])
	})
}

func TestNotificationHandler_GetPreferences(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/preferences/", handler.GetPreferences)

	t.Run("successful get preferences", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/preferences/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		// Default preferences should have standard fields
		assert.Contains(t, response, "task_due_soon")
		assert.Contains(t, response, "task_overdue")
		assert.Contains(t, response, "task_completed")
	})
}

func TestNotificationHandler_UpdatePreferences(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.PUT("/preferences/", handler.UpdatePreferences)

	t.Run("successful update preferences", func(t *testing.T) {
		req := map[string]interface{}{
			"task_due_soon": false,
			"task_overdue":  true,
		}
		w := testutil.MakeRequest(e, "PUT", "/api/notifications/preferences/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)
		assert.Equal(t, false, response["task_due_soon"])
		assert.Equal(t, true, response["task_overdue"])
	})
}

func TestNotificationHandler_RegisterDevice(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/devices/", handler.RegisterDevice)

	t.Run("successful device registration", func(t *testing.T) {
		req := map[string]interface{}{
			"name":            "iPhone 15",
			"device_id":       "test-device-id-123",
			"registration_id": "test-registration-id-abc",
			"platform":        "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusCreated)
	})

	t.Run("missing required fields returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"name": "iPhone 15",
			// Missing device_id, registration_id, platform
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid platform returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"device_id":       "test-device-id-456",
			"registration_id": "test-registration-id-def",
			"platform":        "windows", // invalid
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestNotificationHandler_ListDevices(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/devices/", handler.ListDevices)

	t.Run("successful list devices", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/notifications/devices/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestNotificationHandler_UnregisterDevice(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/devices/unregister/", handler.UnregisterDevice)

	t.Run("missing registration_id returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"platform": "ios",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("missing platform returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"registration_id": "test-id",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid platform returns 400", func(t *testing.T) {
		req := map[string]interface{}{
			"registration_id": "test-id",
			"platform":        "windows",
		}
		w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestNotificationHandler_DeleteDevice(t *testing.T) {
	handler, e, db := setupNotificationHandler(t)
	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")

	authGroup := e.Group("/api/notifications")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.DELETE("/devices/:id/", handler.DeleteDevice)

	t.Run("missing platform query param returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/1/", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid platform returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/1/?platform=windows", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("invalid id returns 400", func(t *testing.T) {
		w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/invalid/?platform=ios", nil, "test-token")
		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}
