//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package storage

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"ragflow/internal/utility"
	"testing"
	"time"

	"ragflow/internal/server"
)

// getMinioConfig returns MinIO configuration for testing
// Configuration can be loaded from environment variables or config file
func getMinioConfig() (*server.MinioConfig, error) {

	// Initialize configuration
	if err := server.Init(""); err != nil {
		return nil, err
	}

	// Try to get configuration from environment variables first
	config := server.GetConfig().StorageEngine.Minio

	log.Printf("MinioConfig: %+v", config)
	return config, nil
}

// getEnv gets environment variable or returns default value
func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

// getEnvBool gets environment variable as bool or returns default value
func getEnvBool(key string, defaultValue bool) bool {
	if value := os.Getenv(key); value != "" {
		return value == "true" || value == "1" || value == "yes"
	}
	return defaultValue
}

// newTestMinioStorage creates a new MinIO storage instance for testing
func newTestMinioStorage(t *testing.T) *MinioStorage {
	rootDir := utility.GetProjectRoot()
	t.Chdir(rootDir)
	t.Chdir(rootDir)

	config, err := getMinioConfig()

	if err != nil {
		t.Skipf("Skipping test: failed to get MinIO configuration: %v", err)
		return nil
	}
	storage, err := NewMinioStorage(config)
	if err != nil {
		t.Skipf("Skipping test: failed to connect to MinIO: %v", err)
	}
	return storage
}

func TestNewMinioStorage(t *testing.T) {
	rootDir := utility.GetProjectRoot()
	t.Chdir(rootDir)

	config, err := getMinioConfig()
	if err != nil {
		t.Skipf("Skipping test: failed to get MinIO configuration: %v", err)
		return
	}

	storage, err := NewMinioStorage(config)
	if err != nil {
		t.Skipf("Skipping test: failed to connect to MinIO: %v", err)
	}

	if storage == nil {
		t.Error("Expected storage to be non-nil")
	}

	if storage.client == nil {
		t.Error("Expected client to be non-nil")
	}

	if storage.config == nil {
		t.Error("Expected config to be non-nil")
	}
}

func TestNewMinioStorage_InvalidConfig(t *testing.T) {
	// Test with invalid host
	config := &server.MinioConfig{
		Host:     "invalid-host:99999",
		User:     "test",
		Password: "test",
		Secure:   false,
	}

	_, err := NewMinioStorage(config)
	// Should return an error for invalid connection
	if err == nil {
		t.Log("Note: Connection may succeed but fail later depending on network timeout")
	}
}

func TestMinioStorage_Health(t *testing.T) {
	storage := newTestMinioStorage(t)

	healthy := storage.Health()
	// Health check should return true if connection is working
	// Note: This depends on whether a default bucket is configured
	t.Logf("Health check result: %v", healthy)
	if !healthy {
		t.Error("Expected storage to be healthy")
	}
}

func TestMinioStorage_PutAndGet(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "test-file.txt"
	content := []byte("Hello, MinIO Test!")

	// Test Put
	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	// Test Get
	retrieved, err := storage.Get(bucket, key)
	if err != nil {
		t.Fatalf("Failed to get object: %v", err)
	}

	if !bytes.Equal(retrieved, content) {
		t.Errorf("Retrieved content does not match. Expected %s, got %s", content, retrieved)
	}

	// Cleanup
	err = storage.Remove(bucket, key)
	if err != nil {
		t.Logf("Warning: failed to cleanup test object: %v", err)
	}
}

func TestMinioStorage_Put_EmptyData(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "empty-file.txt"
	content := []byte{}

	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put empty object: %v", err)
	}

	// Verify object exists
	exists := storage.ObjExist(bucket, key)
	if !exists {
		t.Error("Expected empty object to exist")
	}

	// Cleanup
	storage.Remove(bucket, key)
}

func TestMinioStorage_Put_LargeData(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "large-file.bin"
	// Create 1MB of data
	content := make([]byte, 1024*1024)
	for i := range content {
		content[i] = byte(i % 256)
	}

	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put large object: %v", err)
	}

	retrieved, err := storage.Get(bucket, key)
	if err != nil {
		t.Fatalf("Failed to get large object: %v", err)
	}

	if !bytes.Equal(retrieved, content) {
		t.Error("Retrieved large content does not match original")
	}

	// Cleanup
	storage.Remove(bucket, key)
}

func TestMinioStorage_Get_NonExistent(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "non-existent-file.txt"

	_, err := storage.Get(bucket, key)
	if err == nil {
		t.Error("Expected error when getting non-existent object")
	}
}

func TestMinioStorage_Remove(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "file-to-delete.txt"
	content := []byte("Delete me")

	// First, put an object
	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	// Verify it exists
	exists := storage.ObjExist(bucket, key)
	if !exists {
		t.Fatal("Expected object to exist before removal")
	}

	// Remove it
	err = storage.Remove(bucket, key)
	if err != nil {
		t.Fatalf("Failed to remove object: %v", err)
	}

	// Verify it's gone
	exists = storage.ObjExist(bucket, key)
	if exists {
		t.Error("Expected object to not exist after removal")
	}
}

func TestMinioStorage_Remove_NonExistent(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "non-existent-file.txt"

	// Removing a non-existent object should not error
	err := storage.Remove(bucket, key)
	if err != nil {
		t.Logf("Remove non-existent object returned error (may be acceptable): %v", err)
	}
}

func TestMinioStorage_ObjExist(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "existence-test.txt"
	content := []byte("Test content")

	// Check non-existent object
	exists := storage.ObjExist(bucket, key)
	if exists {
		t.Error("Expected non-existent object to return false")
	}

	// Create object
	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	// Check existing object
	exists = storage.ObjExist(bucket, key)
	if !exists {
		t.Error("Expected existing object to return true")
	}

	// Cleanup
	storage.Remove(bucket, key)
}

func TestMinioStorage_GetPresignedURL(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "presigned-test.txt"
	content := []byte("Presigned URL test content")

	// Create object first
	err := storage.Put(bucket, key, content)
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	// Get presigned URL
	url, err := storage.GetPresignedURL(bucket, key, 5*time.Minute)
	if err != nil {
		t.Fatalf("Failed to get presigned URL: %v", err)
	}

	if url == "" {
		t.Error("Expected presigned URL to be non-empty")
	}

	// Verify URL contains expected components
	if len(url) > 0 {
		t.Logf("Generated presigned URL (first 100 chars): %s...", url[:min(100, len(url))])
	}

	// Cleanup
	storage.Remove(bucket, key)
}

func TestMinioStorage_GetPresignedURL_NonExistent(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "non-existent-presigned.txt"

	_, err := storage.GetPresignedURL(bucket, key, 5*time.Minute)
	if err == nil {
		t.Log("Note: Some MinIO versions may allow presigned URLs for non-existent objects")
	}
}

func TestMinioStorage_BucketExists(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := fmt.Sprintf("test-bucket-exists-%d", time.Now().Unix())

	// Check non-existent bucket
	exists := storage.BucketExists(bucket)
	if exists {
		t.Error("Expected non-existent bucket to return false")
	}

	// Create bucket by putting an object
	err := storage.Put(bucket, "test.txt", []byte("test"))
	if err != nil {
		t.Fatalf("Failed to create bucket: %v", err)
	}

	// Check existing bucket
	exists = storage.BucketExists(bucket)
	if !exists {
		t.Error("Expected existing bucket to return true")
	}

	// Cleanup
	storage.RemoveBucket(bucket)
}

func TestMinioStorage_RemoveBucket(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := fmt.Sprintf("test-bucket-remove-%d", time.Now().Unix())

	// Create bucket with some objects
	err := storage.Put(bucket, "file1.txt", []byte("content1"))
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	err = storage.Put(bucket, "file2.txt", []byte("content2"))
	if err != nil {
		t.Fatalf("Failed to put object: %v", err)
	}

	// Verify bucket exists
	exists := storage.BucketExists(bucket)
	if !exists {
		t.Fatal("Expected bucket to exist before removal")
	}

	// Remove bucket
	err = storage.RemoveBucket(bucket)
	if err != nil {
		t.Fatalf("Failed to remove bucket: %v", err)
	}

	// Verify bucket is gone
	exists = storage.BucketExists(bucket)
	if exists {
		t.Error("Expected bucket to not exist after removal")
	}
}

func TestMinioStorage_Copy(t *testing.T) {
	storage := newTestMinioStorage(t)

	srcBucket := "test-bucket-src"
	srcKey := "source-file.txt"
	destBucket := "test-bucket-dest"
	destKey := "copied-file.txt"
	content := []byte("Content to copy")

	// Create source object
	err := storage.Put(srcBucket, srcKey, content)
	if err != nil {
		t.Fatalf("Failed to put source object: %v", err)
	}

	// Copy object
	success := storage.Copy(srcBucket, srcKey, destBucket, destKey)
	if !success {
		t.Fatal("Failed to copy object")
	}

	// Verify destination exists
	exists := storage.ObjExist(destBucket, destKey)
	if !exists {
		t.Error("Expected copied object to exist")
	}

	// Verify content matches
	retrieved, err := storage.Get(destBucket, destKey)
	if err != nil {
		t.Fatalf("Failed to get copied object: %v", err)
	}

	if !bytes.Equal(retrieved, content) {
		t.Error("Copied content does not match original")
	}

	// Cleanup
	storage.Remove(srcBucket, srcKey)
	storage.Remove(destBucket, destKey)
}

func TestMinioStorage_Copy_NonExistentSource(t *testing.T) {
	storage := newTestMinioStorage(t)

	srcBucket := "test-bucket-src"
	srcKey := "non-existent-source.txt"
	destBucket := "test-bucket-dest"
	destKey := "should-not-exist.txt"

	success := storage.Copy(srcBucket, srcKey, destBucket, destKey)
	if success {
		t.Error("Expected copy of non-existent object to fail")
	}

	// Verify destination does not exist
	exists := storage.ObjExist(destBucket, destKey)
	if exists {
		t.Error("Expected destination object to not exist after failed copy")
		storage.Remove(destBucket, destKey)
	}
}

func TestMinioStorage_Move(t *testing.T) {
	storage := newTestMinioStorage(t)

	srcBucket := "test-bucket-src"
	srcKey := "file-to-move.txt"
	destBucket := "test-bucket-dest"
	destKey := "moved-file.txt"
	content := []byte("Content to move")

	// Create source object
	err := storage.Put(srcBucket, srcKey, content)
	if err != nil {
		t.Fatalf("Failed to put source object: %v", err)
	}

	// Move object
	success := storage.Move(srcBucket, srcKey, destBucket, destKey)
	if !success {
		t.Fatal("Failed to move object")
	}

	// Verify source is gone
	exists := storage.ObjExist(srcBucket, srcKey)
	if exists {
		t.Error("Expected source object to not exist after move")
	}

	// Verify destination exists
	exists = storage.ObjExist(destBucket, destKey)
	if !exists {
		t.Error("Expected moved object to exist")
	}

	// Verify content matches
	retrieved, err := storage.Get(destBucket, destKey)
	if err != nil {
		t.Fatalf("Failed to get moved object: %v", err)
	}

	if !bytes.Equal(retrieved, content) {
		t.Error("Moved content does not match original")
	}

	// Cleanup
	storage.Remove(destBucket, destKey)
}

func TestMinioStorage_Move_NonExistentSource(t *testing.T) {
	storage := newTestMinioStorage(t)

	srcBucket := "test-bucket-src"
	srcKey := "non-existent-source.txt"
	destBucket := "test-bucket-dest"
	destKey := "should-not-exist.txt"

	success := storage.Move(srcBucket, srcKey, destBucket, destKey)
	if success {
		t.Error("Expected move of non-existent object to fail")
	}
}

func TestMinioStorage_MultipleObjectsInBucket(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := fmt.Sprintf("test-multi-%d", time.Now().Unix())
	numObjects := 10

	// Create multiple objects
	for i := 0; i < numObjects; i++ {
		key := fmt.Sprintf("file-%d.txt", i)
		content := []byte(fmt.Sprintf("Content %d", i))
		err := storage.Put(bucket, key, content)
		if err != nil {
			t.Fatalf("Failed to put object %d: %v", i, err)
		}
	}

	// Verify all objects exist
	for i := 0; i < numObjects; i++ {
		key := fmt.Sprintf("file-%d.txt", i)
		exists := storage.ObjExist(bucket, key)
		if !exists {
			t.Errorf("Expected object %s to exist", key)
		}
	}

	// Verify content
	for i := 0; i < numObjects; i++ {
		key := fmt.Sprintf("file-%d.txt", i)
		expectedContent := []byte(fmt.Sprintf("Content %d", i))
		retrieved, err := storage.Get(bucket, key)
		if err != nil {
			t.Errorf("Failed to get object %s: %v", key, err)
			continue
		}
		if !bytes.Equal(retrieved, expectedContent) {
			t.Errorf("Content mismatch for object %s", key)
		}
	}

	// Cleanup - remove bucket with all objects
	err := storage.RemoveBucket(bucket)
	if err != nil {
		t.Logf("Warning: failed to cleanup bucket: %v", err)
	}
}

func TestMinioStorage_SpecialCharactersInKey(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	specialKeys := []string{
		"file with spaces.txt",
		"file-with-dashes.txt",
		"file_with_underscores.txt",
		"file.multiple.dots.txt",
		"path/to/nested/file.txt",
		"unicode-文件.txt",
	}

	for _, key := range specialKeys {
		content := []byte(fmt.Sprintf("Content for %s", key))

		err := storage.Put(bucket, key, content)
		if err != nil {
			t.Errorf("Failed to put object with key '%s': %v", key, err)
			continue
		}

		retrieved, err := storage.Get(bucket, key)
		if err != nil {
			t.Errorf("Failed to get object with key '%s': %v", key, err)
			continue
		}

		if !bytes.Equal(retrieved, content) {
			t.Errorf("Content mismatch for key '%s'", key)
		}

		// Cleanup
		storage.Remove(bucket, key)
	}
}

func TestMinioStorage_TenantID(t *testing.T) {
	storage := newTestMinioStorage(t)

	bucket := "test-bucket"
	key := "tenant-test.txt"
	content := []byte("Tenant test content")
	tenantID := "tenant-123"

	// Put with tenant ID
	err := storage.Put(bucket, key, content, tenantID)
	if err != nil {
		t.Fatalf("Failed to put object with tenant ID: %v", err)
	}

	// Get with tenant ID
	retrieved, err := storage.Get(bucket, key, tenantID)
	if err != nil {
		t.Fatalf("Failed to get object with tenant ID: %v", err)
	}

	if !bytes.Equal(retrieved, content) {
		t.Error("Content mismatch for tenant-specific object")
	}

	// Check existence with tenant ID
	exists := storage.ObjExist(bucket, key, tenantID)
	if !exists {
		t.Error("Expected object to exist with tenant ID")
	}

	// Cleanup
	storage.Remove(bucket, key, tenantID)
}

// min is a helper function to get the minimum of two integers
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
