package auth

import (
	"errors"
	"fmt"

	"github.com/gin-gonic/gin"

	"armature/store"
)

// ErrNoCert is returned when no client certificate is presented on a
// connection that requires mTLS authentication.
var ErrNoCert = errors.New("no client certificate presented")

// MTLSNativeConfig holds configuration for the native (non-proxy) mTLS provider.
type MTLSNativeConfig struct {
	AutoActivate bool   // auto-activate new users (default true)
	DefaultTeam  string // team ID for auto-provisioned users (optional)
}

// MTLSNativeProvider authenticates by reading the peer certificate
// directly from the TLS connection state. Unlike MTLSProxyProvider,
// it does not trust headers — identity is cryptographically verified
// by the Go TLS stack before the HTTP layer runs.
//
// Requires TLS_MODE=mtls so the binary terminates TLS itself.
type MTLSNativeProvider struct {
	cfg MTLSNativeConfig
}

// NewMTLSNativeProvider creates a native mTLS auth provider.
func NewMTLSNativeProvider(cfg MTLSNativeConfig) *MTLSNativeProvider {
	return &MTLSNativeProvider{cfg: cfg}
}

func (p *MTLSNativeProvider) Mode() Mode { return ModeMTLS }

func (p *MTLSNativeProvider) SupportsRegistration() bool { return false }

func (p *MTLSNativeProvider) Register(_ *gin.Context, _ store.Stores) (*Result, error) {
	return nil, ErrNotSupported
}

// Authenticate reads the verified peer certificate from the TLS connection
// state. The CN becomes the username, and sha256(cert.Raw) is the stable
// external_id. Returns ErrNoCert when no TLS or no peer certificates.
func (p *MTLSNativeProvider) Authenticate(c *gin.Context, stores store.Stores) (*Result, error) {
	if c.Request.TLS == nil || len(c.Request.TLS.PeerCertificates) == 0 {
		return nil, fmt.Errorf("%w", ErrNoCert)
	}

	peer := c.Request.TLS.PeerCertificates[0]

	cn := peer.Subject.CommonName
	if cn == "" {
		return nil, fmt.Errorf("%w: certificate has no CommonName", ErrInvalidCreds)
	}

	// Build DN fields from the certificate subject for resolveOrProvision
	dnFields := map[string]string{"CN": cn}
	if len(peer.EmailAddresses) > 0 {
		dnFields["emailAddress"] = peer.EmailAddresses[0]
	}

	fingerprint := FingerprintCert(peer)

	return resolveOrProvision(
		c.Request.Context(), stores, cn, dnFields, fingerprint,
		p.cfg.AutoActivate, p.cfg.DefaultTeam,
	)
}
