package middleware

import (
	"net/http"
	"strings"
)

// CSRFProtect returns middleware that validates the Origin header on
// state-changing methods (POST, PUT, PATCH, DELETE).
//
// An origin is trusted if:
// 1. It's in the explicit additionalOrigins list, OR
// 2. Its hostname matches the configured host, OR
// 3. Its hostname matches the request's own Host header (same-origin check)
func CSRFProtect(host, port string, additionalOrigins []string) func(http.Handler) http.Handler {
	originSet := make(map[string]struct{})
	for _, o := range additionalOrigins {
		o = strings.TrimSpace(o)
		if o != "" {
			originSet[o] = struct{}{}
		}
	}

	hostname := host
	if idx := strings.LastIndex(hostname, ":"); idx > 0 {
		hostname = hostname[:idx]
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			switch r.Method {
			case "POST", "PUT", "PATCH", "DELETE":
				origin := r.Header.Get("Origin")
				if origin == "" {
					referer := r.Header.Get("Referer")
					if referer != "" {
						for o := range originSet {
							if strings.HasPrefix(referer, o) {
								origin = o
								break
							}
						}
					}
				}

				if origin != "" {
					// Extract request host for same-origin check
					reqHost := r.Host
					if idx := strings.LastIndex(reqHost, ":"); idx > 0 {
						reqHost = reqHost[:idx]
					}

					if !isOriginTrusted(origin, hostname, port, reqHost, originSet) {
						http.Error(w, `{"error":"forbidden origin"}`, http.StatusForbidden)
						return
					}
				}
			}
			next.ServeHTTP(w, r)
		})
	}
}

func isOriginTrusted(origin, host, port, reqHost string, originSet map[string]struct{}) bool {
	if _, ok := originSet[origin]; ok {
		return true
	}

	withoutScheme := origin
	if idx := strings.Index(withoutScheme, "://"); idx >= 0 {
		withoutScheme = withoutScheme[idx+3:]
	}
	if idx := strings.Index(withoutScheme, "/"); idx >= 0 {
		withoutScheme = withoutScheme[:idx]
	}

	originHost := withoutScheme
	originPort := ""
	if idx := strings.LastIndex(originHost, ":"); idx >= 0 {
		originPort = originHost[idx+1:]
		originHost = originHost[:idx]
	}

	originHost = strings.TrimPrefix(originHost, "[")
	originHost = strings.TrimSuffix(originHost, "]")

	// Match configured host
	if host != "" && originHost == host {
		return true
	}

	// Match request's own Host header (same-origin)
	if reqHost != "" && originHost == reqHost {
		return true
	}

	// Localhost variants
	if originHost == "localhost" || originHost == "127.0.0.1" || originHost == "::1" {
		if port == "" || originPort == "" || originPort == port {
			return true
		}
	}

	return false
}
