//go:build integration
// +build integration

package restheadspec

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/gorilla/mux"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	"github.com/bitechdev/ResolveSpec/pkg/common"
)

// Test models
type TestUser struct {
	ID        uint       `gorm:"primaryKey" json:"id"`
	Name      string     `gorm:"not null" json:"name"`
	Email     string     `gorm:"uniqueIndex;not null" json:"email"`
	Age       int        `json:"age"`
	Active    bool       `gorm:"default:true" json:"active"`
	CreatedAt time.Time  `json:"created_at"`
	Posts     []TestPost `gorm:"foreignKey:UserID" json:"posts,omitempty"`
}

func (TestUser) TableName() string {
	return "test_users"
}

type TestPost struct {
	ID        uint          `gorm:"primaryKey" json:"id"`
	UserID    uint          `gorm:"not null" json:"user_id"`
	Title     string        `gorm:"not null" json:"title"`
	Content   string        `json:"content"`
	Published bool          `gorm:"default:false" json:"published"`
	CreatedAt time.Time     `json:"created_at"`
	User      *TestUser     `gorm:"foreignKey:UserID" json:"user,omitempty"`
	Comments  []TestComment `gorm:"foreignKey:PostID" json:"comments,omitempty"`
}

func (TestPost) TableName() string {
	return "test_posts"
}

type TestComment struct {
	ID        uint      `gorm:"primaryKey" json:"id"`
	PostID    uint      `gorm:"not null" json:"post_id"`
	Content   string    `gorm:"not null" json:"content"`
	CreatedAt time.Time `json:"created_at"`
	Post      *TestPost `gorm:"foreignKey:PostID" json:"post,omitempty"`
}

func (TestComment) TableName() string {
	return "test_comments"
}

// Test helper functions
func setupTestDB(t *testing.T) *gorm.DB {
	// Get connection string from environment or use default
	dsn := os.Getenv("TEST_DATABASE_URL")
	if dsn == "" {
		dsn = "host=localhost user=postgres password=postgres dbname=restheadspec_test port=5434 sslmode=disable"
	}

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Skipf("Skipping integration test: database not available: %v", err)
		return nil
	}

	// Run migrations
	err = db.AutoMigrate(&TestUser{}, &TestPost{}, &TestComment{})
	if err != nil {
		t.Skipf("Skipping integration test: failed to migrate database: %v", err)
		return nil
	}

	return db
}

func cleanupTestDB(t *testing.T, db *gorm.DB) {
	// Clean up test data
	db.Exec("TRUNCATE TABLE test_comments CASCADE")
	db.Exec("TRUNCATE TABLE test_posts CASCADE")
	db.Exec("TRUNCATE TABLE test_users CASCADE")
}

func createTestData(t *testing.T, db *gorm.DB) {
	users := []TestUser{
		{Name: "John Doe", Email: "john@example.com", Age: 30, Active: true},
		{Name: "Jane Smith", Email: "jane@example.com", Age: 25, Active: true},
		{Name: "Bob Johnson", Email: "bob@example.com", Age: 35, Active: false},
	}

	for i := range users {
		if err := db.Create(&users[i]).Error; err != nil {
			t.Fatalf("Failed to create test user: %v", err)
		}
	}

	posts := []TestPost{
		{UserID: users[0].ID, Title: "First Post", Content: "Hello World", Published: true},
		{UserID: users[0].ID, Title: "Second Post", Content: "More content", Published: true},
		{UserID: users[1].ID, Title: "Jane's Post", Content: "Jane's content", Published: false},
	}

	for i := range posts {
		if err := db.Create(&posts[i]).Error; err != nil {
			t.Fatalf("Failed to create test post: %v", err)
		}
	}

	comments := []TestComment{
		{PostID: posts[0].ID, Content: "Great post!"},
		{PostID: posts[0].ID, Content: "Thanks for sharing"},
		{PostID: posts[1].ID, Content: "Interesting"},
	}

	for i := range comments {
		if err := db.Create(&comments[i]).Error; err != nil {
			t.Fatalf("Failed to create test comment: %v", err)
		}
	}
}

// Integration tests
func TestIntegration_GetAllUsers(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("GET", "/public/test_users", nil)
	req.Header.Set("X-DetailApi", "true")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	if response.Metadata == nil {
		t.Fatal("Expected metadata, got nil")
	}

	if response.Metadata.Total != 3 {
		t.Errorf("Expected 3 users, got %d", response.Metadata.Total)
	}
}

func TestIntegration_GetUsersWithFilters(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	// Filter: age > 25
	req := httptest.NewRequest("GET", "/public/test_users", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-SearchOp-Gt-Age", "25")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Should return 2 users (John: 30, Bob: 35)
	if response.Metadata.Total != 2 {
		t.Errorf("Expected 2 filtered users, got %d", response.Metadata.Total)
	}
}

func TestIntegration_GetUsersWithPagination(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("GET", "/public/test_users", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-Limit", "2")
	req.Header.Set("X-Offset", "1")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

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

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Total should still be 3, but we're only retrieving 2 records starting from offset 1
	if response.Metadata.Total != 3 {
		t.Errorf("Expected total 3 users, got %d", response.Metadata.Total)
	}

	if response.Metadata.Limit != 2 {
		t.Errorf("Expected limit 2, got %d", response.Metadata.Limit)
	}

	if response.Metadata.Offset != 1 {
		t.Errorf("Expected offset 1, got %d", response.Metadata.Offset)
	}
}

func TestIntegration_GetUsersWithSorting(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	// Sort by age descending
	req := httptest.NewRequest("GET", "/public/test_users", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-Sort", "-age")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

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

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Parse data to verify sort order
	dataBytes, _ := json.Marshal(response.Data)
	var users []TestUser
	json.Unmarshal(dataBytes, &users)

	if len(users) < 3 {
		t.Fatal("Expected at least 3 users")
	}

	// Check that users are sorted by age descending (Bob:35, John:30, Jane:25)
	if users[0].Age < users[1].Age || users[1].Age < users[2].Age {
		t.Error("Expected users to be sorted by age descending")
	}
}

func TestIntegration_GetUsersWithColumnsSelection(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("GET", "/public/test_users", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-Columns", "id,name,email")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Verify data was returned (column selection doesn't affect metadata count)
	if response.Metadata.Total != 3 {
		t.Errorf("Expected 3 users, got %d", response.Metadata.Total)
	}
}

func TestIntegration_GetUsersWithPreload(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("GET", "/public/test_users?x-fieldfilter-email=john@example.com", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-Preload", "Posts")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Verify posts are preloaded
	dataBytes, _ := json.Marshal(response.Data)
	var users []TestUser
	json.Unmarshal(dataBytes, &users)

	if len(users) == 0 {
		t.Fatal("Expected at least one user")
	}

	if len(users[0].Posts) == 0 {
		t.Error("Expected posts to be preloaded")
	}
}

func TestIntegration_GetMetadata(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("GET", "/public/test_users/metadata", nil)
	req.Header.Set("X-DetailApi", "true")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if !(w.Code == http.StatusOK || w.Code == http.StatusPartialContent) {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v. Body: %s, Error: %v", response.Success, w.Body.String(), response.Error)
	}

	// Check that metadata includes columns
	metadataBytes, _ := json.Marshal(response.Data)
	var metadata common.TableMetadata
	json.Unmarshal(metadataBytes, &metadata)

	if len(metadata.Columns) == 0 {
		t.Error("Expected metadata to contain columns")
	}

	// Verify some expected columns
	hasID := false
	hasName := false
	hasEmail := false
	for _, col := range metadata.Columns {
		if col.Name == "id" {
			hasID = true
		}
		if col.Name == "name" {
			hasName = true
		}
		if col.Name == "email" {
			hasEmail = true
		}
	}

	if !hasID || !hasName || !hasEmail {
		t.Error("Expected metadata to contain 'id', 'name', and 'email' columns")
	}
}

func TestIntegration_OptionsRequest(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	req := httptest.NewRequest("OPTIONS", "/public/test_users", nil)
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

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

	// Check CORS headers
	if w.Header().Get("Access-Control-Allow-Origin") == "" {
		t.Error("Expected Access-Control-Allow-Origin header")
	}

	if w.Header().Get("Access-Control-Allow-Methods") == "" {
		t.Error("Expected Access-Control-Allow-Methods header")
	}
}

func TestIntegration_QueryParamsOverHeaders(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	// Query params should override headers
	req := httptest.NewRequest("GET", "/public/test_users?x-limit=1", nil)
	req.Header.Set("X-DetailApi", "true")
	req.Header.Set("X-Limit", "10")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

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

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Query param should win (limit=1)
	if response.Metadata.Limit != 1 {
		t.Errorf("Expected limit 1 from query param, got %d", response.Metadata.Limit)
	}
}

func TestIntegration_GetSingleRecord(t *testing.T) {
	db := setupTestDB(t)
	defer cleanupTestDB(t, db)
	createTestData(t, db)

	handler := NewHandlerWithGORM(db)
	handler.registry.RegisterModel("public.test_users", TestUser{})

	muxRouter := mux.NewRouter()
	SetupMuxRoutes(muxRouter, handler, nil)

	// Get first user ID
	var user TestUser
	db.Where("email = ?", "john@example.com").First(&user)

	req := httptest.NewRequest("GET", fmt.Sprintf("/public/test_users/%d", user.ID), nil)
	req.Header.Set("X-DetailApi", "true")
	w := httptest.NewRecorder()

	muxRouter.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d. Body: %s", w.Code, w.Body.String())
	}

	var response common.Response
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("Failed to parse response: %v", err)
	}

	if !response.Success {
		t.Errorf("Expected success=true, got %v", response.Success)
	}

	// Verify it's a single record
	dataBytes, _ := json.Marshal(response.Data)
	var resultUser TestUser
	json.Unmarshal(dataBytes, &resultUser)

	if resultUser.Email != "john@example.com" {
		t.Errorf("Expected user with email 'john@example.com', got '%s'", resultUser.Email)
	}
}
