package staticweb

import (
	"fmt"
	"net/http"
	"sort"
	"strings"
	"sync"
)

// service implements the StaticFileService interface.
type service struct {
	mounts map[string]*mountPoint // urlPrefix -> mount point
	config *ServiceConfig
	mu     sync.RWMutex
}

// NewService creates a new static file service with the given configuration.
// If config is nil, default configuration is used.
func NewService(config *ServiceConfig) StaticFileService {
	if config == nil {
		config = DefaultServiceConfig()
	}

	return &service{
		mounts: make(map[string]*mountPoint),
		config: config,
	}
}

// Mount adds a new mount point with the given configuration.
func (s *service) Mount(config MountConfig) error {
	// Validate the config
	if err := s.validateMountConfig(config); err != nil {
		return err
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	// Check if the prefix is already mounted
	if _, exists := s.mounts[config.URLPrefix]; exists {
		return fmt.Errorf("mount point already exists at %s", config.URLPrefix)
	}

	// Create the mount point
	mp, err := newMountPoint(config, s.config)
	if err != nil {
		return fmt.Errorf("failed to create mount point: %w", err)
	}

	// Add to the map
	s.mounts[config.URLPrefix] = mp

	return nil
}

// Unmount removes the mount point at the given URL prefix.
func (s *service) Unmount(urlPrefix string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	mp, exists := s.mounts[urlPrefix]
	if !exists {
		return fmt.Errorf("no mount point exists at %s", urlPrefix)
	}

	// Close the mount point to release resources
	if err := mp.Close(); err != nil {
		return fmt.Errorf("failed to close mount point: %w", err)
	}

	// Remove from the map
	delete(s.mounts, urlPrefix)

	return nil
}

// ListMounts returns a sorted list of all mounted URL prefixes.
func (s *service) ListMounts() []string {
	s.mu.RLock()
	defer s.mu.RUnlock()

	prefixes := make([]string, 0, len(s.mounts))
	for prefix := range s.mounts {
		prefixes = append(prefixes, prefix)
	}

	sort.Strings(prefixes)
	return prefixes
}

// Reload reinitializes all filesystem providers that support reloading.
// This is useful when the underlying files have changed (e.g., zip file updated).
// Providers that implement ReloadableProvider will be reloaded.
func (s *service) Reload() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var errors []error

	// Reload all mount points that support it
	for prefix, mp := range s.mounts {
		if reloadable, ok := mp.provider.(ReloadableProvider); ok {
			if err := reloadable.Reload(); err != nil {
				errors = append(errors, fmt.Errorf("%s: %w", prefix, err))
			}
		}
	}

	// Return combined errors if any
	if len(errors) > 0 {
		return fmt.Errorf("errors while reloading providers: %v", errors)
	}

	return nil
}

// Close releases all resources held by the service.
func (s *service) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	var errors []error

	// Close all mount points
	for prefix, mp := range s.mounts {
		if err := mp.Close(); err != nil {
			errors = append(errors, fmt.Errorf("%s: %w", prefix, err))
		}
	}

	// Clear the map
	s.mounts = make(map[string]*mountPoint)

	// Return combined errors if any
	if len(errors) > 0 {
		return fmt.Errorf("errors while closing mount points: %v", errors)
	}

	return nil
}

// Handler returns an http.Handler that serves static files from all mount points.
func (s *service) Handler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s.mu.RLock()
		defer s.mu.RUnlock()

		// Find the best matching mount point using longest-prefix matching
		var bestMatch *mountPoint
		var bestPrefix string

		for prefix, mp := range s.mounts {
			if strings.HasPrefix(r.URL.Path, prefix) {
				if len(prefix) > len(bestPrefix) {
					bestMatch = mp
					bestPrefix = prefix
				}
			}
		}

		// If no mount point matches, return without writing a response
		// This allows other handlers (like API routes) to process the request
		if bestMatch == nil {
			return
		}

		// Serve the file from the matched mount point
		bestMatch.ServeHTTP(w, r)
	})
}

// validateMountConfig validates the mount configuration.
func (s *service) validateMountConfig(config MountConfig) error {
	if config.URLPrefix == "" {
		return fmt.Errorf("URLPrefix cannot be empty")
	}

	if !strings.HasPrefix(config.URLPrefix, "/") {
		return fmt.Errorf("URLPrefix must start with /")
	}

	if config.Provider == nil {
		return fmt.Errorf("provider cannot be nil")
	}

	return nil
}
