package gateway

import (
	"fmt"
	"net/http"

	"github.com/google/uuid"
	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/buildwithgrove/path/observation"
)

const (
	// ***************************************
	// ** General HTTP Headers **
	// ***************************************

	// This UUID is generated by Envoy Proxy (GUARD) and passed to PATH via the HTTP headers.
	// Ref: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-request-id
	httpHeaderEnvoyTraceID = "X-Request-Id"

	// APIVersionPrefix is the prefix for the API version.
	// It is used and removed by the routing middleware.
	//
	// Example:
	//
	// 	/v1/{rest_path_1} -> /{rest_path_1}
	// 	/v1/{rest_path_1}/{rest_path_2} -> /{rest_path_1}/{rest_path_2}
	// 	/v1/{rest_path_1}/{rest_path_2}/... -> /{rest_path_1}/{rest_path_2}/...
	APIVersionPrefix = "/v1"

	// ***************************************
	// ** Grove Portal HTTP Headers **
	// TODO_TECHDEBT: Isolate all Portal (Grove specific) headers into a separate
	// package/file/directory to make it ultra clear how these work.
	// ***************************************

	// HttpHeaderPortalAccountID is the header key for the portal account ID.
	httpHeaderPortalAccountID = "Portal-Account-ID"

	// HttpHeaderPortalAppID is the header key for the portal app ID.
	//
	// It is also used by the routing middleware in `router.go` to ensure
	// the portal app ID is not present in the request path that is forwarded to
	// the service endpoint.
	//
	// Example:
	//
	//  /1a2b3c4d/path/segment -> /path/segment
	//  /1a2b3c4d/path -> /path
	HttpHeaderPortalAppID = "Portal-Application-ID"

	// Portal API Key HTTP header.
	HttpHeaderAuthorization = "Authorization"

	// ***************************************
	// ** Regional HTTP Headers & Constants **
	// ***************************************

	// Auth Region HTTP header.
	httpHeaderAuthRegion = "Auth-Region"

	// TODO_MVP(@adshmh): Implement proper region handling when region fields are passed to PATH.
	// Uncomment the following as part of handling region fields.
	regionNamePlaceholder = "region-unspecified" // Used when no region is specified
	// regionNorthAmericaEast1 = "northamerica-northeast1" // North America Northeast region
	// regionEuropeNorth1      = "europe-north1"           // Europe North region
)

// ---------- User Requests ----------

// getUserRequestGatewayObservations returns gateway-level observations for an organic, i.e. from a user, request.
func getUserRequestGatewayObservations(httpReq *http.Request) *observation.GatewayObservations {
	return &observation.GatewayObservations{
		// Extract Request Authentication fields from HTTP headers.
		RequestAuth:  extractAuthObservationFromHTTPHeaders(httpReq),
		RequestType:  observation.RequestType_REQUEST_TYPE_ORGANIC,
		ReceivedTime: timestamppb.Now(),
	}
}

// extractAuthObservationFromHTTPHeaders extracts authentication information from HTTP headers
// and returns it as a RequestAuth observation object.
//
// Headers extracted:
// - Auth request ID
// - Auth region
// - Portal account ID
// - Portal application ID
func extractAuthObservationFromHTTPHeaders(httpReq *http.Request) *observation.RequestAuth {
	// Extract authentication Trace ID from headers
	traceID := getTraceID(httpReq)

	// Extract region information from headers
	region := httpReq.Header.Get(httpHeaderAuthRegion)

	// Extract portal credentials from headers
	portalAccountID := httpReq.Header.Get(httpHeaderPortalAccountID)
	portalApplicationID := httpReq.Header.Get(HttpHeaderPortalAppID)

	// Construct and return the RequestAuth object with extracted values
	return &observation.RequestAuth{
		TraceId: traceID,
		Region:  region,
		PortalCredentials: &observation.PortalCredentials{
			PortalAccountId:     portalAccountID,
			PortalApplicationId: portalApplicationID,
		},
	}
}

// getTraceID extracts the trace ID from the HTTP headers.
// If the trace ID is not set, it generates a new UUID.
func getTraceID(httpReq *http.Request) string {
	// If the trace ID is set in the header by Envoy, use it.
	if traceID := httpReq.Header.Get(httpHeaderEnvoyTraceID); traceID != "" {
		return traceID
	}

	// If the trace ID is not set, fallback to generating a new UUID.
	// This is a fallback for cases where the trace ID is not set by
	// Envoy. For example, if PATH is running standalone (i.e. not behind Envoy).
	return uuid.New().String()
}

// ---------- Synthetic Requests ----------

// getSyntheticRequestGatewayObservations returns the gateway-level observations for a synthetic request.
// Example: request originated from the hydrator.
func getSyntheticRequestGatewayObservations() *observation.GatewayObservations {
	return &observation.GatewayObservations{
		// Request Authentication fields for synthetic requests.
		RequestAuth:  setAuthObservationForSyntheticRequests(),
		RequestType:  observation.RequestType_REQUEST_TYPE_SYNTHETIC,
		ReceivedTime: timestamppb.Now(),
	}
}

// Sets Authentication metadata for synthetic, i.e. generated by the endpoint hydrator, requests.
func setAuthObservationForSyntheticRequests() *observation.RequestAuth {
	return &observation.RequestAuth{
		TraceId: getSyntheticTraceID(),
		// TODO_MVP(@adshmh): pass the region field to PATH, to apply to synthetic requests' metadata.
		Region: regionNamePlaceholder,
		// No Portal credentials needs to be set.
	}
}

// getSyntheticTraceID generates a trace ID for synthetic requests.
// The trace ID is a UUID prefixed with "synthetic-".
func getSyntheticTraceID() string {
	return fmt.Sprintf("synthetic-%s", uuid.New().String())
}
