/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
:                                                                               :
:   █▀ █ █▀▀   ·   Blazing-fast pentesting suite                                :
:   ▄█ █ █▀    ·   BSD 3-Clause License                                         :
:                                                                               :
:   (c) 2022-2025 vmfunc (vmfunc), xyzeva,                        :
:                 lunchcat alumni & contributors                                :
:                                                                               :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/

/*

   BSD 3-Clause License
   (c) 2022-2025 vmfunc, xyzeva & contributors

*/

package frameworks

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

// Signature represents a pattern to match for framework detection.
type Signature struct {
	Pattern    string
	Weight     float32
	HeaderOnly bool
}

// Detector is the interface for framework detection plugins.
type Detector interface {
	// Name returns the unique framework name.
	Name() string
	// Signatures returns patterns to search for this framework.
	Signatures() []Signature
	// Detect performs detection and returns confidence (0.0-1.0) and version.
	// The version can be empty if not detectable.
	Detect(body string, headers http.Header) (confidence float32, version string)
}

// registry holds all registered detectors.
var (
	registryMu sync.RWMutex
	registry   = make(map[string]Detector)
)

// Register adds a detector to the registry. Should be called from init().
func Register(d Detector) {
	registryMu.Lock()
	defer registryMu.Unlock()
	registry[d.Name()] = d
}

// GetDetectors returns all registered detectors.
func GetDetectors() map[string]Detector {
	registryMu.RLock()
	defer registryMu.RUnlock()

	// Return a copy to prevent mutation
	result := make(map[string]Detector, len(registry))
	for k, v := range registry {
		result[k] = v
	}
	return result
}

// GetDetector returns a specific detector by name.
func GetDetector(name string) (Detector, bool) {
	registryMu.RLock()
	defer registryMu.RUnlock()
	d, ok := registry[name]
	return d, ok
}

// BaseDetector provides common functionality for detector implementations.
type BaseDetector struct {
	name       string
	signatures []Signature
}

// NewBaseDetector creates a new base detector.
func NewBaseDetector(name string, signatures []Signature) BaseDetector {
	return BaseDetector{name: name, signatures: signatures}
}

// Name returns the framework name.
func (b BaseDetector) Name() string {
	return b.name
}

// Signatures returns the detection signatures.
func (b BaseDetector) Signatures() []Signature {
	return b.signatures
}

// MatchSignatures checks body and headers against signatures and returns a weighted score.
func (b BaseDetector) MatchSignatures(body string, headers http.Header) float32 {
	var weightedScore float32
	var totalWeight float32

	for _, sig := range b.signatures {
		totalWeight += sig.Weight

		if sig.HeaderOnly {
			if containsHeader(headers, sig.Pattern) {
				weightedScore += sig.Weight
			}
		} else if strings.Contains(body, sig.Pattern) {
			weightedScore += sig.Weight
		}
	}

	if totalWeight == 0 {
		return 0
	}

	return weightedScore / totalWeight
}

// containsHeader checks if a signature pattern exists in headers.
func containsHeader(headers http.Header, signature string) bool {
	sigLower := strings.ToLower(signature)

	// Check header names
	for name := range headers {
		if strings.Contains(strings.ToLower(name), sigLower) {
			return true
		}
	}

	// Check header values
	for _, values := range headers {
		for _, value := range values {
			if strings.Contains(strings.ToLower(value), sigLower) {
				return true
			}
		}
	}
	return false
}
