package policies

import (
	"path"
	"strings"
)

// NoFallback implements a fallback strategy that never falls back.
// All requests for missing files will result in 404 responses.
type NoFallback struct{}

// NewNoFallback creates a new NoFallback strategy.
func NewNoFallback() *NoFallback {
	return &NoFallback{}
}

// ShouldFallback always returns false.
func (f *NoFallback) ShouldFallback(filePath string) bool {
	return false
}

// GetFallbackPath returns an empty string (never called since ShouldFallback returns false).
func (f *NoFallback) GetFallbackPath(filePath string) string {
	return ""
}

// HTMLFallbackStrategy implements a fallback strategy for Single Page Applications (SPAs).
// It serves a specified HTML file (typically index.html) for non-file requests.
type HTMLFallbackStrategy struct {
	indexFile string
}

// NewHTMLFallbackStrategy creates a new HTMLFallbackStrategy.
// indexFile is the path to the HTML file to serve (e.g., "index.html", "/index.html").
func NewHTMLFallbackStrategy(indexFile string) *HTMLFallbackStrategy {
	return &HTMLFallbackStrategy{
		indexFile: indexFile,
	}
}

// ShouldFallback returns true for requests that don't look like static assets.
func (f *HTMLFallbackStrategy) ShouldFallback(filePath string) bool {
	// Always fall back unless it looks like a static asset
	return !f.isStaticAsset(filePath)
}

// GetFallbackPath returns the index file path.
func (f *HTMLFallbackStrategy) GetFallbackPath(filePath string) string {
	return f.indexFile
}

// isStaticAsset checks if the path looks like a static asset (has a non-HTML file extension).
// Paths with no extension are not considered static assets so fallback logic can resolve
// them to path.html or path/index.html.
func (f *HTMLFallbackStrategy) isStaticAsset(filePath string) bool {
	ext := strings.ToLower(path.Ext(filePath))
	if ext == "" || ext == ".html" || ext == ".htm" {
		return false
	}
	return true
}

// ExtensionBasedFallback implements a fallback strategy that skips fallback for known static file extensions.
// This is the behavior from the original StaticHTMLFallbackHandler.
type ExtensionBasedFallback struct {
	staticExtensions map[string]bool
	fallbackPath     string
}

// NewExtensionBasedFallback creates a new ExtensionBasedFallback strategy.
// staticExtensions is a list of file extensions (with leading dot) that should NOT use fallback.
// fallbackPath is the file to serve when fallback is triggered.
func NewExtensionBasedFallback(staticExtensions []string, fallbackPath string) *ExtensionBasedFallback {
	extMap := make(map[string]bool)
	for _, ext := range staticExtensions {
		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		extMap[strings.ToLower(ext)] = true
	}

	return &ExtensionBasedFallback{
		staticExtensions: extMap,
		fallbackPath:     fallbackPath,
	}
}

// NewDefaultExtensionBasedFallback creates an ExtensionBasedFallback with common web asset extensions.
// This matches the behavior of the original StaticHTMLFallbackHandler.
func NewDefaultExtensionBasedFallback(fallbackPath string) *ExtensionBasedFallback {
	return NewExtensionBasedFallback([]string{
		".js", ".css", ".png", ".svg", ".ico", ".json",
		".jpg", ".jpeg", ".gif", ".woff", ".woff2", ".ttf", ".eot",
	}, fallbackPath)
}

// ShouldFallback returns true if the file path doesn't have a static asset extension.
func (f *ExtensionBasedFallback) ShouldFallback(filePath string) bool {
	ext := strings.ToLower(path.Ext(filePath))

	// If it's a known static extension, don't fallback
	if f.staticExtensions[ext] {
		return false
	}

	// Otherwise, try fallback
	return true
}

// GetFallbackPath returns the configured fallback path.
func (f *ExtensionBasedFallback) GetFallbackPath(filePath string) string {
	return f.fallbackPath
}

// HTMLExtensionFallback implements a fallback strategy that appends .html to paths.
// This tries to serve {path}.html for missing files.
type HTMLExtensionFallback struct {
	staticExtensions map[string]bool
}

// NewHTMLExtensionFallback creates a new HTMLExtensionFallback strategy.
func NewHTMLExtensionFallback(staticExtensions []string) *HTMLExtensionFallback {
	extMap := make(map[string]bool)
	for _, ext := range staticExtensions {
		if !strings.HasPrefix(ext, ".") {
			ext = "." + ext
		}
		extMap[strings.ToLower(ext)] = true
	}

	return &HTMLExtensionFallback{
		staticExtensions: extMap,
	}
}

// ShouldFallback returns true if the path doesn't have a static extension or .html.
func (f *HTMLExtensionFallback) ShouldFallback(filePath string) bool {
	ext := strings.ToLower(path.Ext(filePath))

	// If it's a known static extension, don't fallback
	if f.staticExtensions[ext] {
		return false
	}

	// If it already has .html, don't fallback
	if ext == ".html" || ext == ".htm" {
		return false
	}

	return true
}

// GetFallbackPath returns the path with .html appended.
func (f *HTMLExtensionFallback) GetFallbackPath(filePath string) string {
	cleanPath := path.Clean(filePath)
	if !strings.HasSuffix(filePath, "/") {
		cleanPath = strings.TrimRight(cleanPath, "/")
	}

	if !strings.HasSuffix(strings.ToLower(cleanPath), ".html") {
		return cleanPath + ".html"
	}

	return cleanPath
}
