package storage

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// FSStorage implements the Interface for local filesystem storage
// used for testing purpose
type FSStorage struct {
	storageInfo StorageInfo
}

var _ Interface = &FSStorage{}

// NewFSStorage creates a new filesystem storage implementation
func NewFSStorage(path string) *FSStorage {
	storageInfo := ParseStoragePath(path)

	return &FSStorage{
		storageInfo: storageInfo,
	}
}

// DeleteFile deletes the specified file from local filesystem
func (fs *FSStorage) DeleteFile(ctx context.Context, filePath string) error {
	fullPath := filepath.Join(fs.storageInfo.Prefix, filePath)
	err := os.Remove(fullPath)
	if err != nil {
		return fmt.Errorf("failed to delete file %s: %w", fullPath, err)
	}
	return nil
}

// DeleteDir deletes all files with the given prefix path.
func (fs *FSStorage) DeleteDir(ctx context.Context, prefix string) error {
	fullPath := filepath.Join(fs.storageInfo.Prefix, prefix)
	err := os.RemoveAll(fullPath)
	if err != nil {
		return fmt.Errorf("failed to delete directory %s: %w", fullPath, err)
	}
	return nil
}

// CreateFile creates a new file with the provided data on local filesystem
func (fs *FSStorage) CreateFile(ctx context.Context, filePath string, data []byte) error {
	fullPath := filepath.Join(fs.storageInfo.Prefix, filePath)

	// Ensure directory exists
	dir := filepath.Dir(fullPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory %s: %w", dir, err)
	}

	// Write file
	//nolint:gosec
	err := os.WriteFile(fullPath, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write file %s: %w", fullPath, err)
	}

	return nil
}

// Exists checks if a file exists on local filesystem
func (fs *FSStorage) Exists(ctx context.Context, filePath string) (bool, error) {
	fullPath := filepath.Join(fs.storageInfo.Prefix, filePath)
	_, err := os.Stat(fullPath)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, fmt.Errorf("failed to check if file exists %s: %w", fullPath, err)
}

// ReadFile reads the contents of a file from local filesystem
func (fs *FSStorage) ReadFile(ctx context.Context, filePath string) ([]byte, error) {
	fullPath := filepath.Join(fs.storageInfo.Prefix, filePath)
	data, err := os.ReadFile(fullPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read file %s: %w", fullPath, err)
	}
	return data, nil
}

// ListFiles lists all files with the given prefix from local filesystem
func (fs *FSStorage) ListFiles(ctx context.Context, prefix string) ([]string, error) {
	prefixPath := filepath.Join(fs.storageInfo.Prefix, prefix)

	// Get the directory part of the prefix
	prefixDir := filepath.Dir(prefixPath)

	var files []string
	err := filepath.Walk(prefixDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Skip directories
		if info.IsDir() {
			return nil
		}

		// Get relative path
		relPath, err := filepath.Rel(fs.storageInfo.Prefix, path)
		if err != nil {
			return err
		}

		// Use forward slashes for consistency
		relPath = filepath.ToSlash(relPath)

		// Check if path has the correct prefix and apply filters if any.
		if prefix == "" || strings.HasPrefix(relPath, prefix) {
			files = append(files, relPath)
		}

		return nil
	})

	if err != nil && !os.IsNotExist(err) {
		return nil, fmt.Errorf("failed to list files: %w", err)
	}

	return files, nil
}

// GetFullPath returns the full path for the given filePath.
func (fs *FSStorage) GetFullPath(filePath string) string {
	return fs.storageInfo.FullPath + filePath
}
