package services

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"

	"github.com/treytartt/honeydue-api/internal/config"
)

func setupTestStorage(t *testing.T, encrypt bool) (*StorageService, string) {
	t.Helper()

	tmpDir := t.TempDir()

	cfg := &config.StorageConfig{
		UploadDir:    tmpDir,
		BaseURL:      "/uploads",
		MaxFileSize:  10 * 1024 * 1024,
		AllowedTypes: "image/jpeg,image/png,application/pdf",
	}

	svc, err := NewStorageService(cfg)
	if err != nil {
		t.Fatalf("failed to create storage service: %v", err)
	}

	if encrypt {
		encSvc, err := NewEncryptionService(validTestKey())
		if err != nil {
			t.Fatalf("failed to create encryption service: %v", err)
		}
		svc.SetEncryptionService(encSvc)
	}

	return svc, tmpDir
}

func TestReadFile_PlainFile(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, false)

	// Write a plain file
	content := []byte("hello world")
	dir := filepath.Join(tmpDir, "images")
	if err := os.WriteFile(filepath.Join(dir, "test.jpg"), content, 0644); err != nil {
		t.Fatal(err)
	}

	data, mimeType, err := svc.ReadFile("/uploads/images/test.jpg")
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}

	if !bytes.Equal(data, content) {
		t.Fatalf("data mismatch: got %q, want %q", data, content)
	}

	if mimeType == "" {
		t.Fatal("expected non-empty MIME type")
	}
}

func TestReadFile_EncryptedFile(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, true)

	// Encrypt and write a file manually (simulating what Upload does)
	originalContent := []byte("sensitive document content here - must be long enough for detection")
	encSvc, _ := NewEncryptionService(validTestKey())
	encrypted, err := encSvc.Encrypt(originalContent)
	if err != nil {
		t.Fatal(err)
	}

	dir := filepath.Join(tmpDir, "documents")
	if err := os.WriteFile(filepath.Join(dir, "test.pdf.enc"), encrypted, 0644); err != nil {
		t.Fatal(err)
	}

	// ReadFile should find the .enc file and decrypt it
	data, _, err := svc.ReadFile("/uploads/documents/test.pdf")
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}

	if !bytes.Equal(data, originalContent) {
		t.Fatalf("decrypted data mismatch: got %q, want %q", data, originalContent)
	}
}

func TestReadFile_EncFilePreferredOverPlain(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, true)

	encSvc, _ := NewEncryptionService(validTestKey())

	plainContent := []byte("plain version")
	encContent := []byte("encrypted version - the correct one")
	encrypted, _ := encSvc.Encrypt(encContent)

	dir := filepath.Join(tmpDir, "images")
	os.WriteFile(filepath.Join(dir, "photo.jpg"), plainContent, 0644)
	os.WriteFile(filepath.Join(dir, "photo.jpg.enc"), encrypted, 0644)

	// Should prefer the .enc file
	data, _, err := svc.ReadFile("/uploads/images/photo.jpg")
	if err != nil {
		t.Fatalf("ReadFile failed: %v", err)
	}

	if !bytes.Equal(data, encContent) {
		t.Fatalf("expected encrypted version content, got %q", data)
	}
}

func TestReadFile_EmptyURL(t *testing.T) {
	svc, _ := setupTestStorage(t, false)

	_, _, err := svc.ReadFile("")
	if err == nil {
		t.Fatal("expected error for empty URL")
	}
}

func TestReadFile_MissingFile(t *testing.T) {
	svc, _ := setupTestStorage(t, false)

	_, _, err := svc.ReadFile("/uploads/images/nonexistent.jpg")
	if err == nil {
		t.Fatal("expected error for missing file")
	}
}

func TestDelete_HandlesEncAndPlain(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, false)

	dir := filepath.Join(tmpDir, "images")

	// Create both plain and .enc files
	os.WriteFile(filepath.Join(dir, "photo.jpg"), []byte("plain"), 0644)
	os.WriteFile(filepath.Join(dir, "photo.jpg.enc"), []byte("encrypted"), 0644)

	err := svc.Delete("/uploads/images/photo.jpg")
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	// Both should be gone
	if _, err := os.Stat(filepath.Join(dir, "photo.jpg")); !os.IsNotExist(err) {
		t.Fatal("plain file should be deleted")
	}
	if _, err := os.Stat(filepath.Join(dir, "photo.jpg.enc")); !os.IsNotExist(err) {
		t.Fatal("encrypted file should be deleted")
	}
}

func TestDelete_NonexistentFile(t *testing.T) {
	svc, _ := setupTestStorage(t, false)

	// Should not error for non-existent files
	err := svc.Delete("/uploads/images/nope.jpg")
	if err != nil {
		t.Fatalf("Delete should not error for non-existent file: %v", err)
	}
}

// === isAllowedType ===

func TestIsAllowedType(t *testing.T) {
	cfg := &config.StorageConfig{
		UploadDir:    t.TempDir(),
		BaseURL:      "/uploads",
		MaxFileSize:  10 * 1024 * 1024,
		AllowedTypes: "image/jpeg,image/png,application/pdf",
	}
	svc := NewStorageServiceForTest(cfg)

	if !svc.isAllowedType("image/jpeg") {
		t.Fatal("image/jpeg should be allowed")
	}
	if !svc.isAllowedType("image/png") {
		t.Fatal("image/png should be allowed")
	}
	if !svc.isAllowedType("application/pdf") {
		t.Fatal("application/pdf should be allowed")
	}
	if svc.isAllowedType("text/html") {
		t.Fatal("text/html should not be allowed")
	}
	if svc.isAllowedType("") {
		t.Fatal("empty MIME should not be allowed")
	}
}

// === mimeTypesCompatible ===

func TestMimeTypesCompatible(t *testing.T) {
	cfg := &config.StorageConfig{
		UploadDir:    t.TempDir(),
		BaseURL:      "/uploads",
		MaxFileSize:  10 * 1024 * 1024,
		AllowedTypes: "image/jpeg",
	}
	svc := NewStorageServiceForTest(cfg)

	// Same primary type
	if !svc.mimeTypesCompatible("image/jpeg", "image/png") {
		t.Fatal("image/* types should be compatible")
	}

	// Different primary types
	if svc.mimeTypesCompatible("image/jpeg", "application/pdf") {
		t.Fatal("image and application should not be compatible")
	}

	// Same exact types
	if !svc.mimeTypesCompatible("application/pdf", "application/octet-stream") {
		t.Fatal("application/* types should be compatible")
	}
}

// === getExtensionFromMimeType ===

func TestGetExtensionFromMimeType(t *testing.T) {
	cfg := &config.StorageConfig{
		UploadDir:    t.TempDir(),
		BaseURL:      "/uploads",
		MaxFileSize:  10 * 1024 * 1024,
		AllowedTypes: "image/jpeg",
	}
	svc := NewStorageServiceForTest(cfg)

	tests := []struct {
		mimeType string
		expected string
	}{
		{"image/jpeg", ".jpg"},
		{"image/png", ".png"},
		{"image/gif", ".gif"},
		{"image/webp", ".webp"},
		{"application/pdf", ".pdf"},
		{"text/html", ""},
		{"unknown/type", ""},
	}

	for _, tt := range tests {
		got := svc.getExtensionFromMimeType(tt.mimeType)
		if got != tt.expected {
			t.Fatalf("getExtensionFromMimeType(%q) = %q, want %q", tt.mimeType, got, tt.expected)
		}
	}
}

// === GetUploadDir ===

func TestGetUploadDir(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, false)
	if svc.GetUploadDir() != tmpDir {
		t.Fatalf("GetUploadDir() = %q, want %q", svc.GetUploadDir(), tmpDir)
	}
}

// === SetEncryptionService ===

func TestSetEncryptionService(t *testing.T) {
	svc, _ := setupTestStorage(t, false)

	// Initially no encryption service
	if svc.encryptionSvc != nil && svc.encryptionSvc.IsEnabled() {
		t.Fatal("encryption should not be enabled initially in plain mode")
	}

	encSvc, err := NewEncryptionService(validTestKey())
	if err != nil {
		t.Fatal(err)
	}
	svc.SetEncryptionService(encSvc)

	if svc.encryptionSvc == nil {
		t.Fatal("encryption service should be set")
	}
}

// === NewStorageServiceForTest ===

func TestNewStorageServiceForTest(t *testing.T) {
	cfg := &config.StorageConfig{
		UploadDir:    "/tmp/test",
		BaseURL:      "/uploads",
		MaxFileSize:  5 * 1024 * 1024,
		AllowedTypes: "image/jpeg, image/png, application/pdf",
	}
	svc := NewStorageServiceForTest(cfg)

	// Should have 3 allowed types (whitespace trimmed)
	if !svc.isAllowedType("image/jpeg") {
		t.Fatal("image/jpeg should be allowed")
	}
	if !svc.isAllowedType("image/png") {
		t.Fatal("image/png should be allowed")
	}
	if !svc.isAllowedType("application/pdf") {
		t.Fatal("application/pdf should be allowed")
	}
}

// === Delete only plain file ===

func TestDelete_OnlyPlainFile(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, false)

	dir := filepath.Join(tmpDir, "images")
	os.WriteFile(filepath.Join(dir, "only-plain.jpg"), []byte("plain"), 0644)

	err := svc.Delete("/uploads/images/only-plain.jpg")
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	if _, err := os.Stat(filepath.Join(dir, "only-plain.jpg")); !os.IsNotExist(err) {
		t.Fatal("plain file should be deleted")
	}
}

// === Delete only enc file ===

func TestDelete_OnlyEncFile(t *testing.T) {
	svc, tmpDir := setupTestStorage(t, false)

	dir := filepath.Join(tmpDir, "documents")
	os.WriteFile(filepath.Join(dir, "secret.pdf.enc"), []byte("encrypted"), 0644)

	err := svc.Delete("/uploads/documents/secret.pdf")
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	if _, err := os.Stat(filepath.Join(dir, "secret.pdf.enc")); !os.IsNotExist(err) {
		t.Fatal("encrypted file should be deleted")
	}
}
