package azure

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	sdklogical "github.com/openbao/openbao/sdk/v2/logical"
	"github.com/stephnangue/warden/framework"
	"github.com/stephnangue/warden/logical"
)

// azureBackend is the streaming backend for Azure provider operations
type azureBackend struct {
	*framework.StreamingBackend
	tlsSkipVerify bool
	caData        string
}

// extractToken extracts Warden token from Authorization Bearer header or X-Warden-Token header
func extractToken(r *http.Request) string {
	// First, check X-Warden-Token header (explicit Warden token)
	if token := r.Header.Get("X-Warden-Token"); token != "" {
		return token
	}

	// Then check Authorization header for Bearer token
	authHeader := r.Header.Get("Authorization")
	if strings.HasPrefix(authHeader, "Bearer ") {
		return strings.TrimPrefix(authHeader, "Bearer ")
	}

	return ""
}

// Factory creates a new Azure provider backend using the logical.Factory pattern
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
	b := &azureBackend{}

	// Create the streaming backend with gateway path for streaming
	b.StreamingBackend = &framework.StreamingBackend{
		StreamingPaths: []*framework.StreamingPath{
			{
				Pattern:         "gateway",
				Handler:         b.handleGatewayStreaming,
				HelpSynopsis:    "Azure Gateway proxy",
				HelpDescription: "Proxies requests to Azure services with Bearer token injection",
			},
			{
				Pattern:         "gateway/.*",
				Handler:         b.handleGatewayStreaming,
				HelpSynopsis:    "Azure Gateway proxy",
				HelpDescription: "Proxies requests to Azure services with Bearer token injection",
			},
			// Role-based gateway paths for implicit auth
			{
				Pattern:         "role/[^/]+/gateway",
				Handler:         b.handleTransparentGatewayStreaming,
				HelpSynopsis:    "Azure Transparent Gateway proxy",
				HelpDescription: "Proxies requests to Azure services with role embedded in URL path",
			},
			{
				Pattern:         "role/[^/]+/gateway/.*",
				Handler:         b.handleTransparentGatewayStreaming,
				HelpSynopsis:    "Azure Transparent Gateway proxy",
				HelpDescription: "Proxies requests to Azure services with role embedded in URL path",
			},
		},
		TransparentConfig: &framework.TransparentConfig{
			AutoAuthPath:    "",
			DefaultAuthRole: "",
		},
		Backend: &framework.Backend{
			Help:           azureBackendHelp,
			BackendType:    "azure",
			BackendClass:   logical.ClassProvider,
			TokenExtractor: extractToken,
			Paths:          b.paths(),
		},
	}

	// Set common fields
	b.Logger = conf.Logger.WithSubsystem("azure")
	b.StorageView = conf.StorageView

	// Initialize reverse proxy with Azure transport (lazily created on first use)
	initTransport()
	b.StreamingBackend.InitProxy(sharedTransport)

	// Register transport shutdown hook for process-level cleanup
	if conf.RegisterShutdownHook != nil {
		conf.RegisterShutdownHook("azure-transport", ShutdownHTTPTransport)
	}

	if err := b.StreamingBackend.Setup(ctx, conf); err != nil {
		return nil, err
	}

	// Set defaults
	b.MaxBodySize = framework.DefaultMaxBodySize
	b.Timeout = framework.DefaultTimeout

	// Apply configuration if provided
	if len(conf.Config) > 0 {
		if err := ValidateConfig(conf.Config); err != nil {
			return nil, fmt.Errorf("invalid configuration: %w", err)
		}
		parsedConfig := parseConfig(conf.Config)
		b.MaxBodySize = parsedConfig.MaxBodySize
		b.Timeout = parsedConfig.Timeout
		b.tlsSkipVerify = parsedConfig.TLSSkipVerify
		b.caData = parsedConfig.CAData

		// Update transport if custom TLS config is set
		if b.tlsSkipVerify || b.caData != "" {
			transport, err := newTransportWithTLS(b.caData, b.tlsSkipVerify)
			if err != nil {
				return nil, fmt.Errorf("invalid TLS configuration: %w", err)
			}
			b.Proxy.Transport = transport
		}

		b.StreamingBackend.SetTransparentConfig(&framework.TransparentConfig{
			AutoAuthPath:    parsedConfig.AutoAuthPath,
			DefaultAuthRole: parsedConfig.DefaultAuthRole,
		})
	}

	return b, nil
}

// Initialize loads persisted config from storage
func (b *azureBackend) Initialize(ctx context.Context) error {
	if b.StorageView == nil {
		return nil
	}

	// Load persisted config from storage
	entry, err := b.StorageView.Get(ctx, "config")
	if err != nil {
		return fmt.Errorf("failed to read config from storage: %w", err)
	}
	if entry != nil {
		var config struct {
			MaxBodySize     int64  `json:"max_body_size"`
			Timeout         string `json:"timeout"`
			TLSSkipVerify   bool   `json:"tls_skip_verify"`
			CAData          string `json:"ca_data"`
			AutoAuthPath    string `json:"auto_auth_path"`
			DefaultAuthRole string `json:"default_role"`
		}
		if err := entry.DecodeJSON(&config); err != nil {
			return fmt.Errorf("failed to decode config: %w", err)
		}
		b.MaxBodySize = config.MaxBodySize
		b.tlsSkipVerify = config.TLSSkipVerify
		b.caData = config.CAData
		if config.Timeout != "" {
			if timeout, err := time.ParseDuration(config.Timeout); err == nil {
				b.Timeout = timeout
			}
		}

		// Update transport if custom TLS config is set
		if b.tlsSkipVerify || b.caData != "" {
			transport, err := newTransportWithTLS(b.caData, b.tlsSkipVerify)
			if err != nil {
				return fmt.Errorf("invalid TLS configuration: %w", err)
			}
			b.Proxy.Transport = transport
		}

		b.StreamingBackend.SetTransparentConfig(&framework.TransparentConfig{
			AutoAuthPath:    config.AutoAuthPath,
			DefaultAuthRole: config.DefaultAuthRole,
		})
	} else {
		// No persisted config — persist the defaults so a newly enabled
		// Azure provider is immediately configured and readable.
		tc := b.TransparentConfig
		defaultEntry, err := sdklogical.StorageEntryJSON("config", map[string]any{
			"max_body_size":   b.MaxBodySize,
			"timeout":         b.Timeout.String(),
			"tls_skip_verify": b.tlsSkipVerify,
			"ca_data":         b.caData,
			"auto_auth_path":  tc.AutoAuthPath,
			"default_role":    tc.DefaultAuthRole,
		})
		if err != nil {
			return fmt.Errorf("failed to create default config entry: %w", err)
		}
		if err := b.StorageView.Put(ctx, defaultEntry); err != nil {
			return fmt.Errorf("failed to persist default config: %w", err)
		}
		b.Logger.Info("persisted default configuration for new Azure provider")
	}
	return nil
}

// paths returns the configuration paths for the Azure provider
func (b *azureBackend) paths() []*framework.Path {
	return []*framework.Path{
		b.pathConfig(),
	}
}

// handleGatewayStreaming handles streaming gateway requests
func (b *azureBackend) handleGatewayStreaming(ctx context.Context, req *logical.Request, fd *framework.FieldData) error {
	b.handleGateway(ctx, req)
	return nil
}

// handleTransparentGatewayStreaming handles gateway requests with implicit auth.
// The implicit auth has already been performed by the core request handler.
// This method rewrites the path and delegates to the standard gateway handler.
func (b *azureBackend) handleTransparentGatewayStreaming(ctx context.Context, req *logical.Request, fd *framework.FieldData) error {
	// Rewrite the path: /role/{role}/gateway/... -> /gateway/...
	// The original path in req.Path is relative to the mount point
	req.Path = b.StreamingBackend.RewriteTransparentPath(req.Path)

	// Also update the HTTP request URL path for the proxy
	if req.HTTPRequest != nil && req.HTTPRequest.URL != nil {
		req.HTTPRequest.URL.Path = b.StreamingBackend.RewriteTransparentPath(req.HTTPRequest.URL.Path)
	}

	// Delegate to standard gateway handler
	b.handleGateway(ctx, req)
	return nil
}

// ValidateConfig validates Azure provider-specific configuration
func ValidateConfig(config map[string]any) error {
	if err := framework.ValidateAllowedKeys(config,
		"max_body_size", "timeout", "tls_skip_verify", "ca_data",
		"auto_auth_path", "default_role"); err != nil {
		return err
	}
	if err := framework.ValidateCommonConfig(config); err != nil {
		return err
	}
	return framework.ValidateTLSConfig(config)
}

// SensitiveConfigFields returns the list of config fields that should be masked in output
func (b *azureBackend) SensitiveConfigFields() []string {
	return []string{"ca_data"}
}

const azureBackendHelp = `
The Azure provider enables proxying requests to Azure services with automatic
credential management and Bearer token injection.

Warden performs implicit authentication on every request and obtains an Azure
AD Bearer token from the credential manager — minted by exchanging the spec's
pre-provisioned service principal credentials — and injects it into the proxied
request's Authorization header. This allows Warden to broker Azure access
without exposing SP credentials to clients.

The gateway path format is:
  /azure/gateway/{azure-host}/{path}

The {azure-host} segment determines which Azure endpoint receives the request.
Any Azure service hostname is accepted; the provider proxies to it over HTTPS.

Examples:
  /azure/gateway/management.azure.com/subscriptions?api-version=2022-12-01
  /azure/gateway/myvault.vault.azure.net/secrets/mysecret?api-version=7.4
  /azure/gateway/mystorage.blob.core.windows.net/container/blob
  /azure/gateway/graph.microsoft.com/v1.0/me

The role can be provided via the X-Warden-Role header, or embedded in
the URL path:
  /azure/role/{role}/gateway/{azure-host}/{path}

Supported Azure services (non-exhaustive):
- Azure Resource Manager (management.azure.com)
- Azure Key Vault (*.vault.azure.net)
- Azure Storage (*.blob.core.windows.net, *.queue.core.windows.net, etc.)
- Microsoft Graph (graph.microsoft.com)

Configuration:
- max_body_size: Maximum request body size (default: 10MB, max: 100MB)
- timeout: Request timeout duration (e.g., '30s', '5m')
- auto_auth_path: Auth mount path for implicit authentication (e.g., 'auth/jwt/')
- default_role: Fallback role when not specified in the URL path
`
