// Package mtls builds mutual-TLS configurations for the one inter-service link
// that crosses an untrusted network: the reverse bot-link between a remote
// Telegram bot and the gateway (pkg/proto/botlink/v1). Both peers present a
// certificate signed by a shared private CA and verify the other against it, so the
// gateway accepts only our bot and the bot trusts only our gateway. Every other
// inter-service hop stays on the trusted internal network and uses plaintext
// (docs/ARCHITECTURE.md §12).
package mtls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"os"
)

// ServerConfig builds a TLS config for the gateway's bot-link listener. It loads
// the server certificate from certFile/keyFile, trusts client certificates signed
// by the CA in caFile, and requires every client to present a valid one.
func ServerConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, fmt.Errorf("mtls: load server keypair: %w", err)
	}
	pool, err := loadCAPool(caFile)
	if err != nil {
		return nil, err
	}
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientCAs:    pool,
		ClientAuth:   tls.RequireAndVerifyClientCert,
		MinVersion:   tls.VersionTLS13,
	}, nil
}

// ClientConfig builds a TLS config for the bot dialing the gateway. It presents the
// client certificate from certFile/keyFile, verifies the gateway's certificate
// against the CA in caFile, and pins the expected serverName.
func ClientConfig(certFile, keyFile, caFile, serverName string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, fmt.Errorf("mtls: load client keypair: %w", err)
	}
	pool, err := loadCAPool(caFile)
	if err != nil {
		return nil, err
	}
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      pool,
		ServerName:   serverName,
		MinVersion:   tls.VersionTLS13,
	}, nil
}

// loadCAPool reads a PEM bundle and returns a certificate pool trusting it.
func loadCAPool(caFile string) (*x509.CertPool, error) {
	pem, err := os.ReadFile(caFile)
	if err != nil {
		return nil, fmt.Errorf("mtls: read CA %s: %w", caFile, err)
	}
	pool := x509.NewCertPool()
	if !pool.AppendCertsFromPEM(pem) {
		return nil, fmt.Errorf("mtls: CA %s contains no certificates", caFile)
	}
	return pool, nil
}
