package rest_test

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

	"github.com/explorer/backend/api/rest"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// setupTestServer creates a test server with a test database
func setupTestServer(t *testing.T) (*rest.Server, *http.ServeMux) {
	// Use test database or in-memory database
	// For now, we'll use a mock approach
	db, err := setupTestDB(t)
	if err != nil {
		t.Skipf("Skipping test: database not available: %v", err)
		return nil, nil
	}

	server := rest.NewServer(db, 138) // ChainID 138
	mux := http.NewServeMux()
	server.SetupRoutes(mux)

	return server, mux
}

// setupTestDB creates a test database connection. Returns (nil, nil) so unit tests
// run without a real DB; handlers use requireDB(w) and return 503 when db is nil.
// For integration tests with a DB, replace this with a real connection (e.g. testcontainers).
func setupTestDB(t *testing.T) (*pgxpool.Pool, error) {
	return nil, nil
}

// TestHealthEndpoint tests the health check endpoint
func TestHealthEndpoint(t *testing.T) {
	_, mux := setupTestServer(t)
	if mux == nil {
		t.Skip("setupTestServer skipped (no DB)")
		return
	}

	req := httptest.NewRequest("GET", "/health", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	// Without DB we get 503 degraded; with DB we get 200
	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	status, _ := response["status"].(string)
	assert.True(t, status == "healthy" || status == "degraded", "status=%s", status)
}

// TestListBlocks tests the blocks list endpoint
func TestListBlocks(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/api/v1/blocks?limit=10&page=1", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	// Without DB returns 503; with DB returns 200 or 500
	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
}

// TestGetBlockByNumber tests getting a block by number
func TestGetBlockByNumber(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/api/v1/blocks/138/1000", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	// Without DB returns 503; with DB returns 200, 404, or 500
	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
}

// TestListTransactions tests the transactions list endpoint
func TestListTransactions(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/api/v1/transactions?limit=10&page=1", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
}

// TestGetTransactionByHash tests getting a transaction by hash
func TestGetTransactionByHash(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/api/v1/transactions/138/0x1234567890abcdef", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
}

// TestSearchEndpoint tests the unified search endpoint
func TestSearchEndpoint(t *testing.T) {
	_, mux := setupTestServer(t)

	testCases := []struct {
		name     string
		query    string
		wantCode int
	}{
		{"block number", "?q=1000", http.StatusOK},
		{"address", "?q=0x1234567890abcdef1234567890abcdef12345678", http.StatusOK},
		{"transaction hash", "?q=0xabcdef1234567890abcdef1234567890abcdef12", http.StatusOK},
		{"empty query", "?q=", http.StatusBadRequest},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", "/api/v1/search"+tc.query, nil)
			w := httptest.NewRecorder()
			mux.ServeHTTP(w, req)

			assert.True(t, w.Code == tc.wantCode || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
		})
	}
}

// TestTrack1Endpoints tests Track 1 (public) endpoints
func TestTrack1Endpoints(t *testing.T) {
	_, mux := setupTestServer(t)

	testCases := []struct {
		name     string
		endpoint string
		method   string
	}{
		{"latest blocks", "/api/v1/track1/blocks/latest", "GET"},
		{"latest transactions", "/api/v1/track1/txs/latest", "GET"},
		{"bridge status", "/api/v1/track1/bridge/status", "GET"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(tc.method, tc.endpoint, nil)
			w := httptest.NewRecorder()
			mux.ServeHTTP(w, req)

			// Track 1 routes not registered in test mux (only SetupRoutes), so 404 is ok; with full setup 200/500
			assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError, "code=%d", w.Code)
		})
	}
}

// TestCORSHeaders tests CORS headers are present
func TestCORSHeaders(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/health", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	// Check for CORS headers (if implemented)
	// This is a placeholder - actual implementation may vary
	assert.NotNil(t, w.Header())
}

// TestErrorHandling tests error responses
func TestErrorHandling(t *testing.T) {
	_, mux := setupTestServer(t)

	// Test invalid block number
	req := httptest.NewRequest("GET", "/api/v1/blocks/138/invalid", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

	assert.True(t, w.Code >= http.StatusBadRequest)

	var errorResponse map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
	if err == nil {
		assert.NotNil(t, errorResponse["error"])
	}
}

// TestPagination tests pagination parameters
func TestPagination(t *testing.T) {
	_, mux := setupTestServer(t)

	testCases := []struct {
		name     string
		query    string
		wantCode int
	}{
		{"valid pagination", "?limit=10&page=1", http.StatusOK},
		{"large limit", "?limit=1000&page=1", http.StatusOK}, // Should be capped
		{"invalid page", "?limit=10&page=0", http.StatusBadRequest},
		{"negative limit", "?limit=-10&page=1", http.StatusBadRequest},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", "/api/v1/blocks"+tc.query, nil)
			w := httptest.NewRecorder()
			mux.ServeHTTP(w, req)

			assert.True(t, w.Code == tc.wantCode || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code)
		})
	}
}

func TestAIContextEndpoint(t *testing.T) {
	_, mux := setupTestServer(t)

	req := httptest.NewRequest("GET", "/api/v1/ai/context?q=cUSDT+bridge", nil)
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

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

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

func TestAIChatEndpointRequiresOpenAIKey(t *testing.T) {
	_, mux := setupTestServer(t)

	body := bytes.NewBufferString(`{"messages":[{"role":"user","content":"What is live on Chain 138?"}]}`)
	req := httptest.NewRequest("POST", "/api/v1/ai/chat", body)
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, req)

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

// TestRequestTimeout tests request timeout handling
func TestRequestTimeout(t *testing.T) {
	// This would test timeout behavior
	// Implementation depends on timeout middleware
	t.Skip("Requires timeout middleware implementation")
}

// BenchmarkListBlocks benchmarks the blocks list endpoint
func BenchmarkListBlocks(b *testing.B) {
	_, mux := setupTestServer(&testing.T{})

	req := httptest.NewRequest("GET", "/api/v1/blocks?limit=10&page=1", nil)

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