package server

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v4"
	"github.com/sirupsen/logrus"

	"git.maze.io/ham/hamview/schema"
)

// setupTestServer creates an Echo instance with routes configured and an in-memory database
func setupTestServer(t *testing.T) (*echo.Echo, *Server) {
	t.Helper()

	logger := logrus.New()
	logger.SetLevel(logrus.WarnLevel)

	// Initialize in-memory SQLite database
	if err := schema.Open("sqlite3", ":memory:"); err != nil {
		t.Fatalf("Failed to open test database: %v", err)
	}

	// Create server instance
	server := &Server{
		listen: "127.0.0.1:0",
		logger: logger,
	}

	// Setup Echo with routes
	e := echo.New()
	e.HideBanner = true
	setupRoutes(server, e)

	return e, server
}

// teardownTestServer cleans up test resources
func teardownTestServer(t *testing.T) {
	t.Helper()
	// Close database connection if needed
	// schema.Close() // Add this method to schema package if needed
}

// TestRadiosEndpoints tests all radio-related endpoints
func TestRadiosEndpoints(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	t.Run("GET /api/v1/radios", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var radios []*schema.Radio
		if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/radios/:protocol", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/radios/aprs", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var radios []*schema.Radio
		if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})
}

// TestAPRSEndpoints tests all APRS-related endpoints
func TestAPRSEndpoints(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	t.Run("GET /api/v1/aprs/packets", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/aprs/packets", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var packets []*schema.APRSPacket
		if err := json.Unmarshal(rec.Body.Bytes(), &packets); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/aprs/packets filters", func(t *testing.T) {
		testCases := []string{
			"?src=OE1ABC",
			"?dst=APRS",
			"?limit=200",
		}

		for _, query := range testCases {
			req := httptest.NewRequest(http.MethodGet, "/api/v1/aprs/packets"+query, nil)
			rec := httptest.NewRecorder()

			e.ServeHTTP(rec, req)

			if rec.Code != http.StatusOK && rec.Code != http.StatusInternalServerError {
				t.Errorf("Expected status %d or %d, got %d", http.StatusOK, http.StatusInternalServerError, rec.Code)
			}
		}
	})
}

// TestMeshCoreEndpoints tests all MeshCore-related endpoints
func TestMeshCoreEndpoints(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	t.Run("GET /api/v1/meshcore", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var stats map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &stats); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/meshcore/groups", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/groups", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var groups []any
		if err := json.Unmarshal(rec.Body.Bytes(), &groups); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/meshcore/nodes", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var nodes []*schema.MeshCoreNode
		if err := json.Unmarshal(rec.Body.Bytes(), &nodes); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/meshcore/nodes?type=chat", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes?type=chat", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var nodes []*schema.MeshCoreNode
		if err := json.Unmarshal(rec.Body.Bytes(), &nodes); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})

	t.Run("GET /api/v1/meshcore/packets", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var packets []*schema.MeshCorePacket
		if err := json.Unmarshal(rec.Body.Bytes(), &packets); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}
	})
}

// TestMeshCoreNodesCloseTo tests the close-to endpoint
func TestMeshCoreNodesCloseTo(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	// First, insert a test node if needed
	// This is a placeholder - you'll need actual test data setup

	t.Run("GET /api/v1/meshcore/nodes/close-to/:publickey", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes/close-to/test_key", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		// May return 404 or error if no data exists, which is fine for skeleton test
		if rec.Code != http.StatusOK && rec.Code != http.StatusNotFound && rec.Code != http.StatusInternalServerError {
			t.Errorf("Expected status %d, %d, or %d, got %d", http.StatusOK, http.StatusNotFound, http.StatusInternalServerError, rec.Code)
		}
	})

	t.Run("GET /api/v1/meshcore/nodes/close-to/:publickey with radius", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes/close-to/test_key?radius=50000", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		// May return 404 or error if no data exists, which is fine for skeleton test
		if rec.Code != http.StatusOK && rec.Code != http.StatusNotFound && rec.Code != http.StatusInternalServerError {
			t.Errorf("Expected status %d, %d, or %d, got %d", http.StatusOK, http.StatusNotFound, http.StatusInternalServerError, rec.Code)
		}
	})
}

// TestMeshCorePacketsWithFilters tests packet endpoint with various query parameters
func TestMeshCorePacketsWithFilters(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	testCases := []struct {
		name       string
		queryParam string
	}{
		{"With hash", "?hash=test_hash"},
		{"With type", "?type=1"},
		{"With type and channel_hash", "?type=1&channel_hash=test_channel"},
		{"No filters", ""},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets"+tc.queryParam, nil)
			rec := httptest.NewRecorder()

			e.ServeHTTP(rec, req)

			if rec.Code != http.StatusOK && rec.Code != http.StatusInternalServerError {
				t.Errorf("Expected status %d or %d, got %d", http.StatusOK, http.StatusInternalServerError, rec.Code)
			}
		})
	}
}

// BenchmarkGetRadios benchmarks the radios endpoint
func BenchmarkGetRadios(b *testing.B) {
	logger := logrus.New()
	logger.SetLevel(logrus.ErrorLevel)

	if err := schema.Open("sqlite3", ":memory:"); err != nil {
		b.Fatalf("Failed to open test database: %v", err)
	}

	server := &Server{
		listen: "127.0.0.1:0",
		logger: logger,
	}

	e := echo.New()
	e.HideBanner = true
	setupRoutes(server, e)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)
	}
}

// BenchmarkGetMeshCorePackets benchmarks the packets endpoint
func BenchmarkGetMeshCorePackets(b *testing.B) {
	logger := logrus.New()
	logger.SetLevel(logrus.ErrorLevel)

	if err := schema.Open("sqlite3", ":memory:"); err != nil {
		b.Fatalf("Failed to open test database: %v", err)
	}

	server := &Server{
		listen: "127.0.0.1:0",
		logger: logger,
	}

	e := echo.New()
	e.HideBanner = true
	setupRoutes(server, e)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets", nil)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rec := httptest.NewRecorder()
		e.ServeHTTP(rec, req)
	}
}

// Example test showing how to populate test data
func TestWithTestData(t *testing.T) {
	e, _ := setupTestServer(t)
	defer teardownTestServer(t)

	// Example: Insert test data
	ctx := context.Background()

	// Example radio insertion (adapt based on your schema package)
	// radio := &schema.Radio{
	// 	ID:       "test-radio-1",
	// 	Protocol: "aprs",
	// 	Name:     "Test Radio",
	// }
	// if err := schema.InsertRadio(ctx, radio); err != nil {
	// 	t.Fatalf("Failed to insert test radio: %v", err)
	// }

	t.Run("GET /api/v1/radios with data", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil)
		rec := httptest.NewRecorder()

		e.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code)
		}

		var radios []*schema.Radio
		if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil {
			t.Errorf("Failed to unmarshal response: %v", err)
		}

		// Add assertions about the data
		// if len(radios) != 1 {
		// 	t.Errorf("Expected 1 radio, got %d", len(radios))
		// }
	})

	_ = ctx // Use ctx to avoid unused variable error
}
