package transport

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"os"
	"path/filepath"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/security"
)

func generateTestCerts(t *testing.T, dir, serverName string) (certPath, keyPath, caPath string) {
	t.Helper()
	ca, err := security.CAInit(dir, "orca-test-ca")
	if err != nil {
		t.Fatalf("CAInit: %v", err)
	}
	keyPEM, csrPEM, err := security.GenerateCSR(serverName, []string{serverName, "127.0.0.1"})
	if err != nil {
		t.Fatalf("GenerateCSR: %v", err)
	}
	signedPEM, err := ca.SignCSR(csrPEM)
	if err != nil {
		t.Fatalf("SignCSR: %v", err)
	}
	certPath = filepath.Join(dir, "server.crt")
	keyPath = filepath.Join(dir, "server.key")
	caPath = filepath.Join(dir, security.CACertFile)
	if err := os.WriteFile(certPath, signedPEM, 0o644); err != nil {
		t.Fatalf("write cert: %v", err)
	}
	if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
		t.Fatalf("write key: %v", err)
	}
	return certPath, keyPath, caPath
}

func TestServerTLSConfig(t *testing.T) {
	dir := t.TempDir()
	certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost")
	cfg, err := security.ServerTLSConfig(certPath, keyPath, caPath)
	if err != nil {
		t.Fatalf("ServerTLSConfig: %v", err)
	}
	if cfg.MinVersion != tls.VersionTLS13 {
		t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13)
	}
	if cfg.MaxVersion != tls.VersionTLS13 {
		t.Errorf("MaxVersion = %d, want %d", cfg.MaxVersion, tls.VersionTLS13)
	}
	if cfg.ClientAuth != tls.RequireAndVerifyClientCert {
		t.Errorf("ClientAuth = %v, want RequireAndVerifyClientCert", cfg.ClientAuth)
	}
	if cfg.ClientCAs == nil {
		t.Error("ClientCAs is nil")
	}
	if len(cfg.CipherSuites) == 0 {
		t.Error("CipherSuites is empty")
	}
}

func TestServerTLSConfig_MissingFiles(t *testing.T) {
	dir := t.TempDir()
	_, err := security.ServerTLSConfig(
		filepath.Join(dir, "nope.crt"),
		filepath.Join(dir, "nope.key"),
		filepath.Join(dir, "nope.ca"),
	)
	if err == nil {
		t.Fatal("expected error for missing files")
	}
}

func TestClientTLSConfig(t *testing.T) {
	dir := t.TempDir()
	certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost")
	cfg, err := security.ClientTLSConfig(caPath, "localhost", certPath, keyPath)
	if err != nil {
		t.Fatalf("ClientTLSConfig: %v", err)
	}
	if cfg.MinVersion != tls.VersionTLS13 {
		t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13)
	}
	if cfg.RootCAs == nil {
		t.Error("RootCAs is nil")
	}
	if cfg.ServerName != "localhost" {
		t.Errorf("ServerName = %q, want localhost", cfg.ServerName)
	}
	if len(cfg.Certificates) != 1 {
		t.Errorf("Certificates len = %d, want 1", len(cfg.Certificates))
	}
}

func TestClientTLSConfig_NoClientCert(t *testing.T) {
	dir := t.TempDir()
	_, _, caPath := generateTestCerts(t, dir, "localhost")
	cfg, err := security.ClientTLSConfig(caPath, "localhost", "", "")
	if err != nil {
		t.Fatalf("ClientTLSConfig: %v", err)
	}
	if len(cfg.Certificates) != 0 {
		t.Errorf("Certificates len = %d, want 0", len(cfg.Certificates))
	}
}

func TestClientTLSConfig_MismatchedCertKey(t *testing.T) {
	dir := t.TempDir()
	_, _, caPath := generateTestCerts(t, dir, "localhost")
	if _, err := security.ClientTLSConfig(caPath, "localhost", "only-cert", ""); err == nil {
		t.Error("expected error for cert without key")
	}
	if _, err := security.ClientTLSConfig(caPath, "localhost", "", "only-key"); err == nil {
		t.Error("expected error for key without cert")
	}
}

func TestNewMTLSClient(t *testing.T) {
	dir := t.TempDir()
	_, _, caPath := generateTestCerts(t, dir, "localhost")
	c, err := NewMTLSClient(caPath, "localhost", "", "")
	if err != nil {
		t.Fatalf("NewMTLSClient: %v", err)
	}
	if c == nil {
		t.Fatal("client is nil")
	}
}

func TestNewMTLSClient_EmptyCAPath(t *testing.T) {
	_, err := NewMTLSClient("", "localhost", "", "")
	if err == nil {
		t.Fatal("expected error for empty caPath")
	}
}

func TestNewMTLSClient_EmptyServerName(t *testing.T) {
	dir := t.TempDir()
	_, _, caPath := generateTestCerts(t, dir, "localhost")
	_, err := NewMTLSClient(caPath, "", "", "")
	if err == nil {
		t.Fatal("expected error for empty serverName")
	}
}

func TestNewMTLSClient_MissingCAFile(t *testing.T) {
	_, err := NewMTLSClient("/nonexistent/ca.crt", "localhost", "", "")
	if err == nil {
		t.Fatal("expected error for missing CA file")
	}
}

func TestMTLSClient_Do_NilReceiver(t *testing.T) {
	var c *MTLSClient
	_, err := c.Do(nil)
	if err == nil {
		t.Fatal("expected error for nil receiver")
	}
}

func TestVerifyPeerCertificate_NoCerts(t *testing.T) {
	cb := VerifyPeerCertificate("expected")
	if err := cb(nil, nil); err == nil {
		t.Error("expected error for no peer certs")
	}
}

func TestVerifyPeerCertificate_Mismatch(t *testing.T) {
	dir := t.TempDir()
	certPath, _, _ := generateTestCerts(t, dir, "localhost")
	certPEM, err := os.ReadFile(certPath)
	if err != nil {
		t.Fatalf("read cert: %v", err)
	}
	block, _ := pem.Decode(certPEM)
	if block == nil {
		t.Fatal("pem.Decode: no cert block")
	}
	cb := VerifyPeerCertificate("wrong-fingerprint")
	if err := cb([][]byte{block.Bytes}, nil); err == nil {
		t.Error("expected error for fingerprint mismatch")
	}
}

func TestVerifyPeerCertificate_Match(t *testing.T) {
	dir := t.TempDir()
	certPath, _, _ := generateTestCerts(t, dir, "localhost")
	certPEM, err := os.ReadFile(certPath)
	if err != nil {
		t.Fatalf("read cert: %v", err)
	}
	block, _ := pem.Decode(certPEM)
	if block == nil {
		t.Fatal("pem.Decode: no cert block")
	}
	leaf, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		t.Fatalf("ParseCertificate: %v", err)
	}
	expected := security.FingerprintOf(leaf.Raw)
	cb := VerifyPeerCertificate(expected)
	if err := cb([][]byte{block.Bytes}, nil); err != nil {
		t.Errorf("expected match, got: %v", err)
	}
}

func TestDialContext_EmptyCAPath(t *testing.T) {
	_, err := DialContext(t.Context(), "tcp", "127.0.0.1:0", "", "localhost")
	if err == nil {
		t.Fatal("expected error for empty caPath")
	}
}

func TestDialContext_ConnectionRefused(t *testing.T) {
	dir := t.TempDir()
	_, _, caPath := generateTestCerts(t, dir, "localhost")
	_, err := DialContext(t.Context(), "tcp", "127.0.0.1:1", caPath, "localhost")
	if err == nil {
		t.Fatal("expected error for connection refused")
	}
}
