package proxmox

import (
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"fmt"
	"os"
	"strings"
)

// TLSConfig describes how the client trusts the Proxmox host's certificate. The
// host serves a self-signed cert by default (proxmox-platform.md §3.1); we do NOT
// blanket-disable verification. Pick exactly one trust mechanism:
//
//   - CAFile: path to a PEM bundle (the PVE CA / a real cert chain) — full verify.
//   - Fingerprint: SHA-256 of the leaf cert (hex, colons optional). Verification is
//     pinned to that exact cert — strong trust for a self-signed host without a CA.
//     The /nodes API returns each node's ssl_fingerprint, which is what to pin.
//   - InsecureSkipVerify: explicitly off by default. Only acceptable for a
//     --selftest against 127.0.0.1; it is named honestly, not hidden behind a flag
//     that sounds benign.
//
// If none is set, standard system verification applies (which will fail on a
// self-signed host — that is the safe default; the operator must pin).
type TLSConfig struct {
	CAFile             string
	Fingerprint        string
	InsecureSkipVerify bool
}

func (t TLSConfig) build() (*tls.Config, error) {
	switch {
	case t.InsecureSkipVerify:
		// Caller opted in explicitly and by an honestly-named field.
		return &tls.Config{InsecureSkipVerify: true}, nil //nolint:gosec // documented, config-gated, off by default

	case t.Fingerprint != "":
		want, err := normalizeFingerprint(t.Fingerprint)
		if err != nil {
			return nil, err
		}
		// Pin to the leaf cert's SHA-256. We disable the default chain check (a
		// self-signed cert has no CA) but enforce an exact-cert match instead, so
		// this is pinning, not "skip verify".
		return &tls.Config{
			InsecureSkipVerify: true, //nolint:gosec // replaced by the pin check below
			VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
				if len(rawCerts) == 0 {
					return fmt.Errorf("proxmox: TLS pin: peer presented no certificate")
				}
				got := sha256.Sum256(rawCerts[0])
				if hex.EncodeToString(got[:]) != want {
					return fmt.Errorf("proxmox: TLS pin mismatch: server cert sha256 does not match configured fingerprint")
				}
				return nil
			},
		}, nil

	case t.CAFile != "":
		pem, err := os.ReadFile(t.CAFile)
		if err != nil {
			return nil, fmt.Errorf("proxmox: reading TLS CA file: %w", err)
		}
		pool := x509.NewCertPool()
		if !pool.AppendCertsFromPEM(pem) {
			return nil, fmt.Errorf("proxmox: TLS CA file %q contained no usable certificates", t.CAFile)
		}
		return &tls.Config{RootCAs: pool}, nil

	default:
		return &tls.Config{}, nil // system roots; safe default
	}
}

// normalizeFingerprint lowercases and strips colons/whitespace, validating that
// the result is a 64-char (32-byte) hex SHA-256.
func normalizeFingerprint(fp string) (string, error) {
	s := strings.ToLower(strings.NewReplacer(":", "", " ", "", "\t", "").Replace(fp))
	if len(s) != 64 {
		return "", fmt.Errorf("proxmox: fingerprint must be a SHA-256 (64 hex chars), got %d", len(s))
	}
	if _, err := hex.DecodeString(s); err != nil {
		return "", fmt.Errorf("proxmox: fingerprint is not valid hex: %w", err)
	}
	return s, nil
}
