package staticweb

import (
	"fmt"
	"io"
	"io/fs"
	"net/http"
	"path"
	"strings"
)

// mountPoint represents a mounted filesystem at a specific URL prefix.
type mountPoint struct {
	urlPrefix        string
	provider         FileSystemProvider
	cachePolicy      CachePolicy
	mimeResolver     MIMETypeResolver
	fallbackStrategy FallbackStrategy
	fileServer       http.Handler
}

// newMountPoint creates a new mount point with the given configuration.
func newMountPoint(config MountConfig, defaults *ServiceConfig) (*mountPoint, error) {
	if config.URLPrefix == "" {
		return nil, fmt.Errorf("URLPrefix cannot be empty")
	}

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

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

	mp := &mountPoint{
		urlPrefix:        config.URLPrefix,
		provider:         config.Provider,
		cachePolicy:      config.CachePolicy,
		mimeResolver:     config.MIMEResolver,
		fallbackStrategy: config.FallbackStrategy,
	}

	// Apply defaults if policies are not specified
	if mp.cachePolicy == nil && defaults != nil {
		mp.cachePolicy = defaultCachePolicy(defaults.DefaultCacheTime)
	}

	if mp.mimeResolver == nil {
		mp.mimeResolver = defaultMIMEResolver()
	}

	// Create an http.FileServer for serving files
	mp.fileServer = http.FileServer(http.FS(config.Provider))

	return mp, nil
}

// ServeHTTP handles HTTP requests for files in this mount point.
func (m *mountPoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Strip the URL prefix to get the file path
	filePath := strings.TrimPrefix(r.URL.Path, m.urlPrefix)
	if filePath == "" {
		filePath = "/"
	}

	// Clean the path
	filePath = path.Clean(filePath)

	// Try to open the file
	file, err := m.provider.Open(strings.TrimPrefix(filePath, "/"))
	if err != nil {
		// For extensionless paths, also try path/index.html
		if path.Ext(filePath) == "" {
			indexFallback := path.Join(filePath, "index.html")
			file, err = m.provider.Open(strings.TrimPrefix(indexFallback, "/"))
			if err == nil {
				defer file.Close()
				m.serveFile(w, r, indexFallback, file)
				return
			}

			indexFallback = fmt.Sprintf("%s.html", filePath)
			file, err = m.provider.Open(strings.TrimPrefix(indexFallback, "/"))
			if err == nil {
				defer file.Close()
				m.serveFile(w, r, indexFallback, file)
				return
			}
		}

		// File doesn't exist - check if we should use fallback
		if m.fallbackStrategy != nil && m.fallbackStrategy.ShouldFallback(filePath) {
			fallbackPath := m.fallbackStrategy.GetFallbackPath(filePath)
			file, err = m.provider.Open(strings.TrimPrefix(fallbackPath, "/"))
			if err == nil {
				defer file.Close()
				m.serveFile(w, r, fallbackPath, file)
				return
			}

		}

		// No fallback or fallback failed - return 404
		http.NotFound(w, r)
		return
	}
	defer file.Close()

	// Serve the file
	m.serveFile(w, r, filePath, file)
}

// serveFile serves a single file with appropriate headers.
func (m *mountPoint) serveFile(w http.ResponseWriter, r *http.Request, filePath string, file fs.File) {
	// Get file info
	stat, err := file.Stat()
	if err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	// If it's a directory, try to serve index.html
	if stat.IsDir() {
		indexPath := path.Join(filePath, "index.html")
		indexFile, err := m.provider.Open(strings.TrimPrefix(indexPath, "/"))
		if err != nil {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		defer indexFile.Close()

		indexStat, err := indexFile.Stat()
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		filePath = indexPath
		stat = indexStat
		file = indexFile
	}

	// Set Content-Type header using MIME resolver
	if m.mimeResolver != nil {
		if mimeType := m.mimeResolver.GetMIMEType(filePath); mimeType != "" {
			w.Header().Set("Content-Type", mimeType)
		}
	}

	// Apply cache policy
	if m.cachePolicy != nil {
		headers := m.cachePolicy.GetCacheHeaders(filePath)
		for key, value := range headers {
			w.Header().Set(key, value)
		}
	}

	// Serve the content
	if seeker, ok := file.(interface {
		io.ReadSeeker
	}); ok {
		http.ServeContent(w, r, stat.Name(), stat.ModTime(), seeker)
	} else {
		// If the file doesn't support seeking, we need to read it all into memory
		data, err := fs.ReadFile(m.provider, strings.TrimPrefix(filePath, "/"))
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}
		http.ServeContent(w, r, stat.Name(), stat.ModTime(), strings.NewReader(string(data)))
	}
}

// Close releases resources held by the mount point.
func (m *mountPoint) Close() error {
	if m.provider != nil {
		return m.provider.Close()
	}
	return nil
}

// defaultCachePolicy creates a default simple cache policy.
func defaultCachePolicy(cacheTime int) CachePolicy {
	// Import the policies package type - we'll need to use the concrete type
	// For now, create a simple inline implementation
	return &simpleCachePolicy{cacheTime: cacheTime}
}

// simpleCachePolicy is a simple inline implementation of CachePolicy
type simpleCachePolicy struct {
	cacheTime int
}

func (p *simpleCachePolicy) GetCacheTime(path string) int {
	return p.cacheTime
}

func (p *simpleCachePolicy) GetCacheHeaders(path string) map[string]string {
	if p.cacheTime <= 0 {
		return map[string]string{
			"Cache-Control": "no-cache, no-store, must-revalidate",
			"Pragma":        "no-cache",
			"Expires":       "0",
		}
	}
	return map[string]string{
		"Cache-Control": fmt.Sprintf("public, max-age=%d", p.cacheTime),
	}
}

// defaultMIMEResolver creates a default MIME resolver.
func defaultMIMEResolver() MIMETypeResolver {
	// Import the policies package type - we'll need to use the concrete type
	// For now, create a simple inline implementation
	return &simpleMIMEResolver{
		types: map[string]string{
			".js":   "application/javascript",
			".mjs":  "application/javascript",
			".cjs":  "application/javascript",
			".css":  "text/css",
			".html": "text/html",
			".htm":  "text/html",
			".json": "application/json",
			".png":  "image/png",
			".jpg":  "image/jpeg",
			".jpeg": "image/jpeg",
			".gif":  "image/gif",
			".svg":  "image/svg+xml",
			".ico":  "image/x-icon",
			".txt":  "text/plain",
		},
	}
}

// simpleMIMEResolver is a simple inline implementation of MIMETypeResolver
type simpleMIMEResolver struct {
	types map[string]string
}

func (r *simpleMIMEResolver) GetMIMEType(filePath string) string {
	ext := strings.ToLower(path.Ext(filePath))
	if mimeType, ok := r.types[ext]; ok {
		return mimeType
	}
	return ""
}

func (r *simpleMIMEResolver) RegisterMIMEType(extension, mimeType string) {
	if !strings.HasPrefix(extension, ".") {
		extension = "." + extension
	}
	r.types[strings.ToLower(extension)] = mimeType
}
