package handlers

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

	"github.com/gin-gonic/gin"
)

func init() {
	gin.SetMode(gin.TestMode)
}

// setupTestRouter creates a test router with handlers
// Note: For full integration tests, use a test database
func setupTestRouter() *gin.Engine {
	router := gin.New()
	return router
}

// TestHealthEndpoint tests the health check endpoint
func TestHealthEndpoint(t *testing.T) {
	router := setupTestRouter()

	// Add health endpoint
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status":  "healthy",
			"service": "consent-service",
			"version": "1.0.0",
		})
	})

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

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

	var response map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &response)

	if response["status"] != "healthy" {
		t.Errorf("Expected status 'healthy', got %v", response["status"])
	}
}

// TestUnauthorizedAccess tests that protected endpoints require auth
func TestUnauthorizedAccess(t *testing.T) {
	router := setupTestRouter()

	// Add a protected endpoint
	router.GET("/api/v1/consent/my", func(c *gin.Context) {
		auth := c.GetHeader("Authorization")
		if auth == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization required"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"consents": []interface{}{}})
	})

	tests := []struct {
		name           string
		authorization  string
		expectedStatus int
	}{
		{"no auth header", "", http.StatusUnauthorized},
		{"empty bearer", "Bearer ", http.StatusOK}, // Would be invalid in real middleware
		{"valid format", "Bearer test-token", http.StatusOK},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, _ := http.NewRequest("GET", "/api/v1/consent/my", nil)
			if tt.authorization != "" {
				req.Header.Set("Authorization", tt.authorization)
			}

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// TestCreateConsentRequest tests consent creation request validation
func TestCreateConsentRequest(t *testing.T) {
	type ConsentRequest struct {
		DocumentType string `json:"document_type"`
		VersionID    string `json:"version_id"`
		Consented    bool   `json:"consented"`
	}

	tests := []struct {
		name        string
		request     ConsentRequest
		expectValid bool
	}{
		{
			name: "valid consent",
			request: ConsentRequest{
				DocumentType: "terms",
				VersionID:    "123e4567-e89b-12d3-a456-426614174000",
				Consented:    true,
			},
			expectValid: true,
		},
		{
			name: "missing document type",
			request: ConsentRequest{
				VersionID: "123e4567-e89b-12d3-a456-426614174000",
				Consented: true,
			},
			expectValid: false,
		},
		{
			name: "missing version ID",
			request: ConsentRequest{
				DocumentType: "terms",
				Consented:    true,
			},
			expectValid: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			isValid := tt.request.DocumentType != "" && tt.request.VersionID != ""
			if isValid != tt.expectValid {
				t.Errorf("Expected valid=%v, got %v", tt.expectValid, isValid)
			}
		})
	}
}

// TestDocumentTypeValidation tests valid document types
func TestDocumentTypeValidation(t *testing.T) {
	validTypes := map[string]bool{
		"terms":                true,
		"privacy":              true,
		"cookies":              true,
		"community_guidelines": true,
		"imprint":              true,
	}

	tests := []struct {
		docType  string
		expected bool
	}{
		{"terms", true},
		{"privacy", true},
		{"cookies", true},
		{"community_guidelines", true},
		{"imprint", true},
		{"invalid", false},
		{"", false},
		{"Terms", false}, // case sensitive
	}

	for _, tt := range tests {
		t.Run(tt.docType, func(t *testing.T) {
			_, isValid := validTypes[tt.docType]
			if isValid != tt.expected {
				t.Errorf("Expected %s valid=%v, got %v", tt.docType, tt.expected, isValid)
			}
		})
	}
}

// TestVersionStatusTransitions tests valid status transitions
func TestVersionStatusTransitions(t *testing.T) {
	validTransitions := map[string][]string{
		"draft":     {"review"},
		"review":    {"approved", "rejected"},
		"approved":  {"scheduled", "published"},
		"scheduled": {"published"},
		"published": {"archived"},
		"rejected":  {"draft"},
		"archived":  {}, // terminal state
	}

	tests := []struct {
		fromStatus string
		toStatus   string
		expected   bool
	}{
		{"draft", "review", true},
		{"draft", "published", false},
		{"review", "approved", true},
		{"review", "rejected", true},
		{"review", "published", false},
		{"approved", "published", true},
		{"approved", "scheduled", true},
		{"published", "archived", true},
		{"published", "draft", false},
		{"archived", "draft", false},
	}

	for _, tt := range tests {
		t.Run(tt.fromStatus+"->"+tt.toStatus, func(t *testing.T) {
			allowed := false
			if transitions, ok := validTransitions[tt.fromStatus]; ok {
				for _, t := range transitions {
					if t == tt.toStatus {
						allowed = true
						break
					}
				}
			}

			if allowed != tt.expected {
				t.Errorf("Transition %s->%s: expected %v, got %v",
					tt.fromStatus, tt.toStatus, tt.expected, allowed)
			}
		})
	}
}

// TestRolePermissions tests role-based access control
func TestRolePermissions(t *testing.T) {
	permissions := map[string]map[string]bool{
		"user": {
			"view_documents":   true,
			"give_consent":     true,
			"view_own_data":    true,
			"request_deletion": true,
			"create_document":  false,
			"publish_version":  false,
			"approve_version":  false,
		},
		"admin": {
			"view_documents":   true,
			"give_consent":     true,
			"view_own_data":    true,
			"create_document":  true,
			"edit_version":     true,
			"publish_version":  true,
			"approve_version":  false, // Only DSB
		},
		"data_protection_officer": {
			"view_documents":   true,
			"create_document":  true,
			"edit_version":     true,
			"approve_version":  true,
			"publish_version":  true,
			"view_audit_log":   true,
		},
	}

	tests := []struct {
		role       string
		action     string
		shouldHave bool
	}{
		{"user", "view_documents", true},
		{"user", "create_document", false},
		{"admin", "create_document", true},
		{"admin", "approve_version", false},
		{"data_protection_officer", "approve_version", true},
	}

	for _, tt := range tests {
		t.Run(tt.role+":"+tt.action, func(t *testing.T) {
			rolePerms, ok := permissions[tt.role]
			if !ok {
				t.Fatalf("Unknown role: %s", tt.role)
			}

			hasPermission := rolePerms[tt.action]
			if hasPermission != tt.shouldHave {
				t.Errorf("Role %s action %s: expected %v, got %v",
					tt.role, tt.action, tt.shouldHave, hasPermission)
			}
		})
	}
}

// TestJSONResponseFormat tests that responses have correct format
func TestJSONResponseFormat(t *testing.T) {
	router := setupTestRouter()

	router.GET("/api/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"success": true,
			"data": gin.H{
				"id":   "123",
				"name": "Test",
			},
		})
	})

	req, _ := http.NewRequest("GET", "/api/test", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	contentType := w.Header().Get("Content-Type")
	if contentType != "application/json; charset=utf-8" {
		t.Errorf("Expected Content-Type 'application/json; charset=utf-8', got %s", contentType)
	}

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	if err != nil {
		t.Fatalf("Response should be valid JSON: %v", err)
	}
}

// TestErrorResponseFormat tests error response format
func TestErrorResponseFormat(t *testing.T) {
	router := setupTestRouter()

	router.GET("/api/error", func(c *gin.Context) {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Invalid input",
		})
	})

	req, _ := http.NewRequest("GET", "/api/error", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &response)

	if response["error"] == nil {
		t.Error("Error response should contain 'error' field")
	}
}

// TestCookieCategoryValidation tests cookie category validation
func TestCookieCategoryValidation(t *testing.T) {
	mandatoryCategories := []string{"necessary"}
	optionalCategories := []string{"functional", "analytics", "marketing"}

	// Necessary should always be consented
	for _, cat := range mandatoryCategories {
		t.Run("mandatory_"+cat, func(t *testing.T) {
			// Business rule: mandatory categories cannot be declined
			isMandatory := true
			if !isMandatory {
				t.Errorf("Category %s should be mandatory", cat)
			}
		})
	}

	// Optional categories can be toggled
	for _, cat := range optionalCategories {
		t.Run("optional_"+cat, func(t *testing.T) {
			isMandatory := false
			if isMandatory {
				t.Errorf("Category %s should not be mandatory", cat)
			}
		})
	}
}

// TestPaginationParams tests pagination parameter handling
func TestPaginationParams(t *testing.T) {
	tests := []struct {
		name     string
		page     int
		perPage  int
		expPage  int
		expLimit int
	}{
		{"defaults", 0, 0, 1, 50},
		{"page 1", 1, 10, 1, 10},
		{"page 5", 5, 20, 5, 20},
		{"negative page", -1, 10, 1, 10},      // should default
		{"too large per_page", 1, 500, 1, 100}, // should cap
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			page := tt.page
			perPage := tt.perPage

			// Apply defaults and limits
			if page < 1 {
				page = 1
			}
			if perPage < 1 {
				perPage = 50
			}
			if perPage > 100 {
				perPage = 100
			}

			if page != tt.expPage {
				t.Errorf("Expected page %d, got %d", tt.expPage, page)
			}
			if perPage != tt.expLimit {
				t.Errorf("Expected perPage %d, got %d", tt.expLimit, perPage)
			}
		})
	}
}

// TestIPAddressExtraction tests IP address extraction from requests
func TestIPAddressExtraction(t *testing.T) {
	tests := []struct {
		name       string
		xForwarded string
		remoteAddr string
		expected   string
	}{
		{"direct connection", "", "192.168.1.1:1234", "192.168.1.1"},
		{"behind proxy", "10.0.0.1", "192.168.1.1:1234", "10.0.0.1"},
		{"multiple proxies", "10.0.0.1, 10.0.0.2", "192.168.1.1:1234", "10.0.0.1"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := setupTestRouter()
			var extractedIP string

			router.GET("/test", func(c *gin.Context) {
				if xf := c.GetHeader("X-Forwarded-For"); xf != "" {
					// Take first IP from list
					for i, ch := range xf {
						if ch == ',' {
							extractedIP = xf[:i]
							break
						}
					}
					if extractedIP == "" {
						extractedIP = xf
					}
				} else {
					// Extract IP from RemoteAddr
					addr := c.Request.RemoteAddr
					for i := len(addr) - 1; i >= 0; i-- {
						if addr[i] == ':' {
							extractedIP = addr[:i]
							break
						}
					}
				}
				c.JSON(http.StatusOK, gin.H{"ip": extractedIP})
			})

			req, _ := http.NewRequest("GET", "/test", nil)
			req.RemoteAddr = tt.remoteAddr
			if tt.xForwarded != "" {
				req.Header.Set("X-Forwarded-For", tt.xForwarded)
			}

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if extractedIP != tt.expected {
				t.Errorf("Expected IP %s, got %s", tt.expected, extractedIP)
			}
		})
	}
}

// TestRequestBodySizeLimit tests that large requests are rejected
func TestRequestBodySizeLimit(t *testing.T) {
	router := setupTestRouter()

	// Simulate a body size limit check
	maxBodySize := int64(1024 * 1024) // 1MB

	router.POST("/api/upload", func(c *gin.Context) {
		if c.Request.ContentLength > maxBodySize {
			c.JSON(http.StatusRequestEntityTooLarge, gin.H{
				"error": "Request body too large",
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{"success": true})
	})

	tests := []struct {
		name           string
		contentLength  int64
		expectedStatus int
	}{
		{"small body", 1000, http.StatusOK},
		{"medium body", 500000, http.StatusOK},
		{"exactly at limit", maxBodySize, http.StatusOK},
		{"over limit", maxBodySize + 1, http.StatusRequestEntityTooLarge},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body := bytes.NewReader(make([]byte, 0))
			req, _ := http.NewRequest("POST", "/api/upload", body)
			req.ContentLength = tt.contentLength

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// ========================================
// EXTENDED HANDLER TESTS
// ========================================

// TestAuthHandlers tests authentication endpoints
func TestAuthHandlers(t *testing.T) {
	router := setupTestRouter()

	// Register endpoint
	router.POST("/api/v1/auth/register", func(c *gin.Context) {
		var req struct {
			Email    string `json:"email"`
			Password string `json:"password"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
			return
		}
		c.JSON(http.StatusCreated, gin.H{"message": "User registered"})
	})

	// Login endpoint
	router.POST("/api/v1/auth/login", func(c *gin.Context) {
		var req struct {
			Email    string `json:"email"`
			Password string `json:"password"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"access_token": "token123"})
	})

	tests := []struct {
		name           string
		endpoint       string
		method         string
		body           interface{}
		expectedStatus int
	}{
		{
			name:     "register - valid",
			endpoint: "/api/v1/auth/register",
			method:   "POST",
			body:     map[string]string{"email": "test@example.com", "password": "password123"},
			expectedStatus: http.StatusCreated,
		},
		{
			name:     "login - valid",
			endpoint: "/api/v1/auth/login",
			method:   "POST",
			body:     map[string]string{"email": "test@example.com", "password": "password123"},
			expectedStatus: http.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			jsonBody, _ := json.Marshal(tt.body)
			req, _ := http.NewRequest(tt.method, tt.endpoint, bytes.NewBuffer(jsonBody))
			req.Header.Set("Content-Type", "application/json")

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// TestDocumentHandlers tests document endpoints
func TestDocumentHandlers(t *testing.T) {
	router := setupTestRouter()

	// GET documents
	router.GET("/api/v1/documents", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"documents": []interface{}{}})
	})

	// GET document by type
	router.GET("/api/v1/documents/:type", func(c *gin.Context) {
		docType := c.Param("type")
		if docType == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid type"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"id": "123", "type": docType})
	})

	tests := []struct {
		name           string
		endpoint       string
		expectedStatus int
	}{
		{"get all documents", "/api/v1/documents", http.StatusOK},
		{"get terms", "/api/v1/documents/terms", http.StatusOK},
		{"get privacy", "/api/v1/documents/privacy", http.StatusOK},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req, _ := http.NewRequest("GET", tt.endpoint, nil)
			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// TestConsentHandlers tests consent endpoints
func TestConsentHandlers(t *testing.T) {
	router := setupTestRouter()

	// Create consent
	router.POST("/api/v1/consent", func(c *gin.Context) {
		var req struct {
			VersionID string `json:"version_id"`
			Consented bool   `json:"consented"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
			return
		}
		c.JSON(http.StatusCreated, gin.H{"message": "Consent saved"})
	})

	// Check consent
	router.GET("/api/v1/consent/check/:type", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"has_consent": true, "needs_update": false})
	})

	tests := []struct {
		name           string
		endpoint       string
		method         string
		body           interface{}
		expectedStatus int
	}{
		{
			name:     "create consent",
			endpoint: "/api/v1/consent",
			method:   "POST",
			body:     map[string]interface{}{"version_id": "123", "consented": true},
			expectedStatus: http.StatusCreated,
		},
		{
			name:           "check consent",
			endpoint:       "/api/v1/consent/check/terms",
			method:         "GET",
			expectedStatus: http.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var req *http.Request
			if tt.body != nil {
				jsonBody, _ := json.Marshal(tt.body)
				req, _ = http.NewRequest(tt.method, tt.endpoint, bytes.NewBuffer(jsonBody))
				req.Header.Set("Content-Type", "application/json")
			} else {
				req, _ = http.NewRequest(tt.method, tt.endpoint, nil)
			}

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// TestAdminHandlers tests admin endpoints
func TestAdminHandlers(t *testing.T) {
	router := setupTestRouter()

	// Create document (admin only)
	router.POST("/api/v1/admin/documents", func(c *gin.Context) {
		auth := c.GetHeader("Authorization")
		if auth != "Bearer admin-token" {
			c.JSON(http.StatusForbidden, gin.H{"error": "Admin only"})
			return
		}
		c.JSON(http.StatusCreated, gin.H{"message": "Document created"})
	})

	tests := []struct {
		name           string
		token          string
		expectedStatus int
	}{
		{"admin token", "Bearer admin-token", http.StatusCreated},
		{"user token", "Bearer user-token", http.StatusForbidden},
		{"no token", "", http.StatusForbidden},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body := map[string]string{"type": "terms", "name": "Test"}
			jsonBody, _ := json.Marshal(body)
			req, _ := http.NewRequest("POST", "/api/v1/admin/documents", bytes.NewBuffer(jsonBody))
			req.Header.Set("Content-Type", "application/json")
			if tt.token != "" {
				req.Header.Set("Authorization", tt.token)
			}

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			if w.Code != tt.expectedStatus {
				t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
			}
		})
	}
}

// TestCORSHeaders tests CORS headers
func TestCORSHeaders(t *testing.T) {
	router := setupTestRouter()

	router.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Next()
	})

	router.GET("/api/test", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "test"})
	})

	req, _ := http.NewRequest("GET", "/api/test", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if w.Header().Get("Access-Control-Allow-Origin") != "*" {
		t.Error("CORS headers not set correctly")
	}
}

// TestRateLimiting tests rate limiting logic
func TestRateLimiting(t *testing.T) {
	requests := 0
	limit := 5

	for i := 0; i < 10; i++ {
		requests++
		if requests > limit {
			// Would return 429 Too Many Requests
			if requests <= limit {
				t.Error("Rate limit not enforced")
			}
		}
	}
}

// TestEmailTemplateHandlers tests email template endpoints
func TestEmailTemplateHandlers(t *testing.T) {
	router := setupTestRouter()

	router.GET("/api/v1/admin/email-templates", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"templates": []interface{}{}})
	})

	router.POST("/api/v1/admin/email-templates/test", func(c *gin.Context) {
		var req struct {
			Recipient string `json:"recipient"`
			VersionID string `json:"version_id"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"message": "Test email sent"})
	})

	req, _ := http.NewRequest("GET", "/api/v1/admin/email-templates", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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