package handlers_test

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/api/handlers"
	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
)

func setupUptimeHandlerTest(t *testing.T) (*gin.Engine, *gorm.DB) {
	t.Helper()
	db := handlers.OpenTestDB(t)
	require.NoError(t, db.AutoMigrate(&models.UptimeMonitor{}, &models.UptimeHeartbeat{}, &models.UptimeHost{}, &models.RemoteServer{}, &models.NotificationProvider{}, &models.Notification{}, &models.ProxyHost{}))

	ns := services.NewNotificationService(db)
	service := services.NewUptimeService(db, ns)
	handler := handlers.NewUptimeHandler(service)

	r := gin.Default()
	api := r.Group("/api/v1")
	uptime := api.Group("/uptime")
	uptime.GET("", handler.List)
	uptime.GET(":id/history", handler.GetHistory)
	uptime.PUT(":id", handler.Update)
	uptime.DELETE(":id", handler.Delete)
	uptime.POST(":id/check", handler.CheckMonitor)
	uptime.POST("/sync", handler.Sync)

	return r, db
}

func TestUptimeHandler_List(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)

	// Seed Monitor
	monitor := models.UptimeMonitor{
		ID:   "monitor-1",
		Name: "Test Monitor",
		Type: "http",
		URL:  "http://example.com",
	}
	db.Create(&monitor)

	req, _ := http.NewRequest("GET", "/api/v1/uptime", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var list []models.UptimeMonitor
	err := json.Unmarshal(w.Body.Bytes(), &list)
	require.NoError(t, err)
	assert.Len(t, list, 1)
	assert.Equal(t, "Test Monitor", list[0].Name)
}

func TestUptimeHandler_GetHistory(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)

	// Seed Monitor and Heartbeats
	monitorID := "monitor-1"
	monitor := models.UptimeMonitor{
		ID:   monitorID,
		Name: "Test Monitor",
	}
	db.Create(&monitor)

	db.Create(&models.UptimeHeartbeat{
		MonitorID: monitorID,
		Status:    "up",
		Latency:   10,
		CreatedAt: time.Now().Add(-1 * time.Minute),
	})
	db.Create(&models.UptimeHeartbeat{
		MonitorID: monitorID,
		Status:    "down",
		Latency:   0,
		CreatedAt: time.Now(),
	})

	req, _ := http.NewRequest("GET", "/api/v1/uptime/"+monitorID+"/history", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var history []models.UptimeHeartbeat
	err := json.Unmarshal(w.Body.Bytes(), &history)
	require.NoError(t, err)
	assert.Len(t, history, 2)
	// Should be ordered by created_at desc
	assert.Equal(t, "down", history[0].Status)
}

func TestUptimeHandler_CheckMonitor(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)

	// Create monitor
	monitor := models.UptimeMonitor{ID: "check-mon-1", Name: "Check Monitor", Type: "http", URL: "http://example.com"}
	db.Create(&monitor)

	req, _ := http.NewRequest("POST", "/api/v1/uptime/check-mon-1/check", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestUptimeHandler_CheckMonitor_NotFound(t *testing.T) {
	r, _ := setupUptimeHandlerTest(t)

	req, _ := http.NewRequest("POST", "/api/v1/uptime/nonexistent/check", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
}

func TestUptimeHandler_Update(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		r, db := setupUptimeHandlerTest(t)

		monitorID := "monitor-update"
		monitor := models.UptimeMonitor{
			ID:         monitorID,
			Name:       "Original Name",
			Interval:   30,
			MaxRetries: 3,
		}
		db.Create(&monitor)

		updates := map[string]interface{}{
			"interval":    60,
			"max_retries": 5,
		}
		body, _ := json.Marshal(updates)

		req, _ := http.NewRequest("PUT", "/api/v1/uptime/"+monitorID, bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)

		var result models.UptimeMonitor
		err := json.Unmarshal(w.Body.Bytes(), &result)
		require.NoError(t, err)
		assert.Equal(t, 60, result.Interval)
		assert.Equal(t, 5, result.MaxRetries)
	})

	t.Run("invalid_json", func(t *testing.T) {
		r, _ := setupUptimeHandlerTest(t)

		req, _ := http.NewRequest("PUT", "/api/v1/uptime/monitor-1", bytes.NewBuffer([]byte("invalid")))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	t.Run("not_found", func(t *testing.T) {
		r, _ := setupUptimeHandlerTest(t)

		updates := map[string]interface{}{
			"interval": 60,
		}
		body, _ := json.Marshal(updates)

		req, _ := http.NewRequest("PUT", "/api/v1/uptime/nonexistent", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusInternalServerError, w.Code)
	})
}

func TestUptimeHandler_DeleteAndSync(t *testing.T) {
	t.Run("delete monitor", func(t *testing.T) {
		r, db := setupUptimeHandlerTest(t)

		monitor := models.UptimeMonitor{ID: "mon-delete", Name: "ToDelete", Type: "http", URL: "http://example.com"}
		db.Create(&monitor)

		req, _ := http.NewRequest("DELETE", "/api/v1/uptime/mon-delete", http.NoBody)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		var m models.UptimeMonitor
		require.Error(t, db.First(&m, "id = ?", "mon-delete").Error)
	})

	t.Run("sync creates monitor for proxy host", func(t *testing.T) {
		r, db := setupUptimeHandlerTest(t)

		// Create a proxy host to be synced to an uptime monitor
		host := models.ProxyHost{UUID: "ph-up-1", Name: "Test Host", DomainNames: "sync.example.com", ForwardHost: "127.0.0.1", ForwardPort: 80, Enabled: true}
		db.Create(&host)

		req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", http.NoBody)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		var monitors []models.UptimeMonitor
		db.Where("proxy_host_id = ?", host.ID).Find(&monitors)
		assert.Len(t, monitors, 1)
		assert.Equal(t, "Test Host", monitors[0].Name)
	})

	t.Run("update enabled via PUT", func(t *testing.T) {
		r, db := setupUptimeHandlerTest(t)

		monitor := models.UptimeMonitor{ID: "mon-enable", Name: "ToToggle", Type: "http", URL: "http://example.com", Enabled: true}
		db.Create(&monitor)

		updates := map[string]interface{}{"enabled": false}
		body, _ := json.Marshal(updates)
		req, _ := http.NewRequest("PUT", "/api/v1/uptime/mon-enable", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		var result models.UptimeMonitor
		err := json.Unmarshal(w.Body.Bytes(), &result)
		require.NoError(t, err)
		assert.False(t, result.Enabled)
	})
}

func TestUptimeHandler_Sync_Success(t *testing.T) {
	r, _ := setupUptimeHandlerTest(t)

	req, _ := http.NewRequest("POST", "/api/v1/uptime/sync", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var result map[string]string
	err := json.Unmarshal(w.Body.Bytes(), &result)
	require.NoError(t, err)
	assert.Equal(t, "Sync started", result["message"])
}

func TestUptimeHandler_Delete_Error(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)
	db.Exec("DROP TABLE IF EXISTS uptime_monitors")

	req, _ := http.NewRequest("DELETE", "/api/v1/uptime/nonexistent", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestUptimeHandler_List_Error(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)
	db.Exec("DROP TABLE IF EXISTS uptime_monitors")

	req, _ := http.NewRequest("GET", "/api/v1/uptime", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestUptimeHandler_GetHistory_Error(t *testing.T) {
	r, db := setupUptimeHandlerTest(t)
	db.Exec("DROP TABLE IF EXISTS uptime_heartbeats")

	req, _ := http.NewRequest("GET", "/api/v1/uptime/monitor-1/history", http.NoBody)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
}
