package backup

import (
	"context"
	"fmt"
	"sync"

	"github.com/stretchr/testify/mock"
	"github.com/tidbcloud/observability/vmbackupmanager/pkg/storage"
)

// MockStorage implements the storage.Interface for testing
type MockStorage struct {
	mock.Mock
	files map[string][]byte
	mu    sync.RWMutex
}

var _ storage.Interface = &MockStorage{}

func NewMockStorage() *MockStorage {
	return &MockStorage{
		files: make(map[string][]byte),
	}
}

func (m *MockStorage) CreateFile(ctx context.Context, filePath string, data []byte) error {
	args := m.Called(ctx, filePath, data)
	if args.Error(0) == nil {
		m.mu.Lock()
		m.files[filePath] = data
		m.mu.Unlock()
	}
	return args.Error(0)
}

func (m *MockStorage) Exists(ctx context.Context, filePath string) (bool, error) {
	args := m.Called(ctx, filePath)
	if args.Error(1) != nil {
		return false, args.Error(1)
	}

	// If mock returns a specific boolean, use it
	if len(args) > 0 && args.Get(0) != nil {
		return args.Bool(0), nil
	}

	// Otherwise check actual files map
	m.mu.RLock()
	_, exists := m.files[filePath]
	m.mu.RUnlock()
	return exists, nil
}

func (m *MockStorage) ReadFile(ctx context.Context, filePath string) ([]byte, error) {
	args := m.Called(ctx, filePath)
	if args.Error(1) != nil {
		return nil, args.Error(1)
	}

	// If mock returns specific data, use it
	if len(args) > 0 && args.Get(0) != nil {
		return args.Get(0).([]byte), nil
	}

	// Otherwise check actual files map
	m.mu.RLock()
	data, exists := m.files[filePath]
	m.mu.RUnlock()
	if !exists {
		return nil, fmt.Errorf("file not found: %s", filePath)
	}
	return data, nil
}

func (m *MockStorage) DeleteFile(ctx context.Context, filePath string) error {
	args := m.Called(ctx, filePath)
	if args.Error(0) == nil {
		m.mu.Lock()
		delete(m.files, filePath)
		m.mu.Unlock()
	}
	return args.Error(0)
}

func (m *MockStorage) DeleteDir(ctx context.Context, prefix string) error {
	args := m.Called(ctx, prefix)
	if args.Error(0) == nil {
		m.mu.Lock()
		for path := range m.files {
			if len(path) >= len(prefix) && path[:len(prefix)] == prefix {
				delete(m.files, path)
			}
		}
		m.mu.Unlock()
	}
	return args.Error(0)
}

func (m *MockStorage) ListFiles(ctx context.Context, prefix string) ([]string, error) {
	args := m.Called(ctx, prefix)
	if args.Error(1) != nil {
		return nil, args.Error(1)
	}

	// If mock returns specific files, use them
	if len(args) > 0 && args.Get(0) != nil {
		return args.Get(0).([]string), nil
	}

	// Otherwise check actual files map
	m.mu.RLock()
	var files []string
	for path := range m.files {
		if len(path) >= len(prefix) && path[:len(prefix)] == prefix {
			files = append(files, path)
		}
	}
	m.mu.RUnlock()
	return files, nil
}

func (m *MockStorage) GetFullPath(filePath string) string {
	args := m.Called(filePath)
	if args.String(0) != "" {
		return args.String(0)
	}
	return "fs:///test-destination/" + filePath
}

// SetFile directly sets a file in the mock storage (for testing convenience)
func (m *MockStorage) SetFile(path string, data []byte) {
	m.mu.Lock()
	m.files[path] = data
	m.mu.Unlock()
}

// SetFileExists sets whether a file exists (for testing convenience)
func (m *MockStorage) SetFileExists(path string, exists bool) {
	m.mu.Lock()
	if exists {
		if _, ok := m.files[path]; !ok {
			m.files[path] = []byte{}
		}
	} else {
		delete(m.files, path)
	}
	m.mu.Unlock()
}
