package proxy

import (
	"context"
	"net/netip"
	"sync"

	"github.com/netbirdio/netbird/proxy/internal/types"
)

type requestContextKey string

const (
	capturedDataKey requestContextKey = "capturedData"
)

// ResponseOrigin indicates where a response was generated.
type ResponseOrigin int

const (
	// OriginBackend means the response came from the backend service.
	OriginBackend ResponseOrigin = iota
	// OriginNoRoute means the proxy had no matching host or path.
	OriginNoRoute
	// OriginProxyError means the proxy failed to reach the backend.
	OriginProxyError
	// OriginAuth means the proxy intercepted the request for authentication.
	OriginAuth
)

func (o ResponseOrigin) String() string {
	switch o {
	case OriginNoRoute:
		return "no_route"
	case OriginProxyError:
		return "proxy_error"
	case OriginAuth:
		return "auth"
	default:
		return "backend"
	}
}

// CapturedData is a mutable struct that allows downstream handlers
// to pass data back up the middleware chain.
type CapturedData struct {
	mu         sync.RWMutex
	requestID  string
	serviceID  types.ServiceID
	accountID  types.AccountID
	origin     ResponseOrigin
	clientIP   netip.Addr
	userID     string
	authMethod string
}

// NewCapturedData creates a CapturedData with the given request ID.
func NewCapturedData(requestID string) *CapturedData {
	return &CapturedData{requestID: requestID}
}

// GetRequestID returns the request ID.
func (c *CapturedData) GetRequestID() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.requestID
}

// SetServiceID sets the service ID.
func (c *CapturedData) SetServiceID(serviceID types.ServiceID) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.serviceID = serviceID
}

// GetServiceID returns the service ID.
func (c *CapturedData) GetServiceID() types.ServiceID {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.serviceID
}

// SetAccountID sets the account ID.
func (c *CapturedData) SetAccountID(accountID types.AccountID) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.accountID = accountID
}

// GetAccountID returns the account ID.
func (c *CapturedData) GetAccountID() types.AccountID {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.accountID
}

// SetOrigin sets the response origin.
func (c *CapturedData) SetOrigin(origin ResponseOrigin) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.origin = origin
}

// GetOrigin returns the response origin.
func (c *CapturedData) GetOrigin() ResponseOrigin {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.origin
}

// SetClientIP sets the resolved client IP.
func (c *CapturedData) SetClientIP(ip netip.Addr) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.clientIP = ip
}

// GetClientIP returns the resolved client IP.
func (c *CapturedData) GetClientIP() netip.Addr {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.clientIP
}

// SetUserID sets the authenticated user ID.
func (c *CapturedData) SetUserID(userID string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.userID = userID
}

// GetUserID returns the authenticated user ID.
func (c *CapturedData) GetUserID() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.userID
}

// SetAuthMethod sets the authentication method used.
func (c *CapturedData) SetAuthMethod(method string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.authMethod = method
}

// GetAuthMethod returns the authentication method used.
func (c *CapturedData) GetAuthMethod() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.authMethod
}

// WithCapturedData adds a CapturedData struct to the context.
func WithCapturedData(ctx context.Context, data *CapturedData) context.Context {
	return context.WithValue(ctx, capturedDataKey, data)
}

// CapturedDataFromContext retrieves the CapturedData from context.
func CapturedDataFromContext(ctx context.Context) *CapturedData {
	v := ctx.Value(capturedDataKey)
	data, ok := v.(*CapturedData)
	if !ok {
		return nil
	}
	return data
}
