package agentd

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/insecure"
)

// Dialer opens an authenticated long-lived connection to the control plane.
type Dialer func(ctx context.Context) (*grpc.ClientConn, error)

// mtlsClientConfig builds the client *tls.Config from the persisted node key/cert
// and pinned CA.
func mtlsClientConfig(cfg Config) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(cfg.CertPath(), cfg.KeyPath())
	if err != nil {
		return nil, fmt.Errorf("agentd: load client keypair: %w", err)
	}
	caPEM, err := os.ReadFile(cfg.CAPath())
	if err != nil {
		return nil, fmt.Errorf("agentd: read CA: %w", err)
	}
	pool := x509.NewCertPool()
	if !pool.AppendCertsFromPEM(caPEM) {
		return nil, errors.New("agentd: CA file contains no certificate")
	}
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      pool,
		ServerName:   cfg.tlsServerName(),
		MinVersion:   tls.VersionTLS13,
	}, nil
}

// tlsServerName returns the SNI to validate the control-plane cert against.
func (c Config) tlsServerName() string {
	if c.ServerName != "" {
		return c.ServerName
	}
	host, _, err := net.SplitHostPort(c.ControlPlaneAddr)
	if err != nil {
		return c.ControlPlaneAddr
	}
	return host
}

// NewMTLSDialer returns a Dialer that connects to the control plane over mTLS,
// presenting the node's client certificate.
func NewMTLSDialer(cfg Config) Dialer {
	return func(ctx context.Context) (*grpc.ClientConn, error) {
		tlsCfg, err := mtlsClientConfig(cfg)
		if err != nil {
			return nil, err
		}
		return grpc.NewClient(cfg.ControlPlaneAddr,
			grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)),
		)
	}
}

// NewEnrollDialer returns an EnrollDialer for the bootstrap (pre-certificate)
// Enroll call. It pins the control-plane CA if ca.crt is already present
// (cloud-init may inject it); otherwise it falls back to a server-unauthenticated
// TLS handshake — the bootstrap token is the trust anchor for that single call.
func NewEnrollDialer(cfg Config) EnrollDialer {
	return func(ctx context.Context) (*grpc.ClientConn, error) {
		var creds credentials.TransportCredentials
		if caPEM, err := os.ReadFile(cfg.CAPath()); err == nil {
			pool := x509.NewCertPool()
			if !pool.AppendCertsFromPEM(caPEM) {
				return nil, errors.New("agentd: pinned CA file invalid")
			}
			creds = credentials.NewTLS(&tls.Config{
				RootCAs:    pool,
				ServerName: cfg.tlsServerName(),
				MinVersion: tls.VersionTLS13,
			})
		} else {
			creds = credentials.NewTLS(&tls.Config{
				InsecureSkipVerify: true, //nolint:gosec // bootstrap-token-authenticated enroll only
				MinVersion:         tls.VersionTLS13,
			})
		}
		return grpc.NewClient(cfg.ControlPlaneAddr, grpc.WithTransportCredentials(creds))
	}
}

// insecureDialer is used only by tests/dev (cfg.Insecure).
func insecureDialer(addr string) Dialer {
	return func(ctx context.Context) (*grpc.ClientConn, error) {
		return grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	}
}
