package ucxi

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// BasicContentStorage provides a basic file-system based implementation of ContentStorage
type BasicContentStorage struct {
	basePath string
	mutex    sync.RWMutex
}

// NewBasicContentStorage creates a new basic content storage
func NewBasicContentStorage(basePath string) (*BasicContentStorage, error) {
	// Ensure base directory exists
	if err := os.MkdirAll(basePath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create storage directory: %w", err)
	}

	return &BasicContentStorage{
		basePath: basePath,
	}, nil
}

// Store stores content with the given key
func (s *BasicContentStorage) Store(ctx context.Context, key string, content *Content) error {
	if key == "" {
		return fmt.Errorf("key cannot be empty")
	}
	if content == nil {
		return fmt.Errorf("content cannot be nil")
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	// Generate file path
	filePath := s.getFilePath(key)

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

	// Calculate checksum if not provided
	if content.Checksum == "" {
		hash := sha256.Sum256(content.Data)
		content.Checksum = hex.EncodeToString(hash[:])
	}

	// Serialize content to JSON
	data, err := json.MarshalIndent(content, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to serialize content: %w", err)
	}

	// Write to file
	if err := ioutil.WriteFile(filePath, data, 0644); err != nil {
		return fmt.Errorf("failed to write content file: %w", err)
	}

	return nil
}

// Retrieve retrieves content by key
func (s *BasicContentStorage) Retrieve(ctx context.Context, key string) (*Content, error) {
	if key == "" {
		return nil, fmt.Errorf("key cannot be empty")
	}

	s.mutex.RLock()
	defer s.mutex.RUnlock()

	filePath := s.getFilePath(key)

	// Check if file exists
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return nil, fmt.Errorf("content not found for key: %s", key)
	}

	// Read file
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read content file: %w", err)
	}

	// Deserialize content
	var content Content
	if err := json.Unmarshal(data, &content); err != nil {
		return nil, fmt.Errorf("failed to deserialize content: %w", err)
	}

	// Verify checksum if available
	if content.Checksum != "" {
		hash := sha256.Sum256(content.Data)
		expectedChecksum := hex.EncodeToString(hash[:])
		if content.Checksum != expectedChecksum {
			return nil, fmt.Errorf("content checksum mismatch")
		}
	}

	return &content, nil
}

// Delete deletes content by key
func (s *BasicContentStorage) Delete(ctx context.Context, key string) error {
	if key == "" {
		return fmt.Errorf("key cannot be empty")
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	filePath := s.getFilePath(key)

	// Check if file exists
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		return fmt.Errorf("content not found for key: %s", key)
	}

	// Remove file
	if err := os.Remove(filePath); err != nil {
		return fmt.Errorf("failed to delete content file: %w", err)
	}

	// Try to remove empty directories
	s.cleanupEmptyDirs(filepath.Dir(filePath))

	return nil
}

// List lists all keys with the given prefix
func (s *BasicContentStorage) List(ctx context.Context, prefix string) ([]string, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	var keys []string

	// Walk through storage directory
	err := filepath.Walk(s.basePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

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

		// Skip non-JSON files
		if !strings.HasSuffix(path, ".json") {
			return nil
		}

		// Convert file path back to key
		relPath, err := filepath.Rel(s.basePath, path)
		if err != nil {
			return err
		}

		// Remove .json extension
		key := strings.TrimSuffix(relPath, ".json")
		
		// Convert file path separators back to key format
		key = strings.ReplaceAll(key, string(filepath.Separator), "/")

		// Check prefix match
		if prefix == "" || strings.HasPrefix(key, prefix) {
			keys = append(keys, key)
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to list storage contents: %w", err)
	}

	return keys, nil
}

// getFilePath converts a storage key to a file path
func (s *BasicContentStorage) getFilePath(key string) string {
	// Sanitize key by replacing potentially problematic characters
	sanitized := strings.ReplaceAll(key, ":", "_")
	sanitized = strings.ReplaceAll(sanitized, "@", "_at_")
	sanitized = strings.ReplaceAll(sanitized, "/", string(filepath.Separator))
	
	return filepath.Join(s.basePath, sanitized+".json")
}

// cleanupEmptyDirs removes empty directories up the tree
func (s *BasicContentStorage) cleanupEmptyDirs(dir string) {
	// Don't remove the base directory
	if dir == s.basePath {
		return
	}

	// Try to remove directory if empty
	if err := os.Remove(dir); err == nil {
		// Successfully removed, try parent
		s.cleanupEmptyDirs(filepath.Dir(dir))
	}
}

// GetStorageStats returns statistics about the storage
func (s *BasicContentStorage) GetStorageStats() (map[string]interface{}, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	var fileCount int
	var totalSize int64

	err := filepath.Walk(s.basePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() && strings.HasSuffix(path, ".json") {
			fileCount++
			totalSize += info.Size()
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to calculate storage stats: %w", err)
	}

	return map[string]interface{}{
		"file_count": fileCount,
		"total_size": totalSize,
		"base_path":  s.basePath,
	}, nil
}

// Exists checks if content exists for the given key
func (s *BasicContentStorage) Exists(ctx context.Context, key string) (bool, error) {
	if key == "" {
		return false, fmt.Errorf("key cannot be empty")
	}

	filePath := s.getFilePath(key)
	
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	_, err := os.Stat(filePath)
	if os.IsNotExist(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("failed to check file existence: %w", err)
	}

	return true, nil
}

// Clear removes all content from storage
func (s *BasicContentStorage) Clear(ctx context.Context) error {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	// Remove all contents of base directory
	entries, err := ioutil.ReadDir(s.basePath)
	if err != nil {
		return fmt.Errorf("failed to read storage directory: %w", err)
	}

	for _, entry := range entries {
		path := filepath.Join(s.basePath, entry.Name())
		if err := os.RemoveAll(path); err != nil {
			return fmt.Errorf("failed to remove %s: %w", path, err)
		}
	}

	return nil
}