package handlers

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"mail_go/internal/db"
)

// makeTestCertPair 生成一对自签名证书（PEM），可选 LF/CRLF 换行。
func makeTestCertPair(t *testing.T, lineEnding string) (certPEM, keyPEM string) {
	t.Helper()

	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("生成私钥失败: %v", err)
	}
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject:      pkix.Name{CommonName: "test"},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(24 * time.Hour),
		DNSNames:     []string{"mail.example.com"},
		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}
	der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("生成证书失败: %v", err)
	}

	certPEM = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
	if lineEnding == "crlf" {
		certPEM = strings.ReplaceAll(certPEM, "\n", "\r\n")
	}
	keyPEM = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
	if lineEnding == "crlf" {
		keyPEM = strings.ReplaceAll(keyPEM, "\n", "\r\n")
	}
	return certPEM, keyPEM
}

// writeExistingCert 模拟已有证书文件（LF 换行）并返回 domain。
func writeExistingCert(t *testing.T, tlsDir string, certPEM, keyPEM string) *db.Domain {
	t.Helper()
	dir := filepath.Join(tlsDir, "1")
	if err := os.MkdirAll(dir, 0700); err != nil {
		t.Fatalf("创建目录失败: %v", err)
	}
	certPath := filepath.Join(dir, "cert.pem")
	keyPath := filepath.Join(dir, "key.pem")
	if err := os.WriteFile(certPath, []byte(certPEM+"\n"), 0644); err != nil {
		t.Fatalf("写入证书失败: %v", err)
	}
	if err := os.WriteFile(keyPath, []byte(keyPEM+"\n"), 0600); err != nil {
		t.Fatalf("写入私钥失败: %v", err)
	}
	return &db.Domain{
		ID:          1,
		Name:        "mail.example.com",
		TlsEnabled:  true,
		TlsCertPath: certPath,
		TlsKeyPath:  keyPath,
	}
}

// TestHandleDomainTLSUpdateUnchangedCertCRLF 复现用户报告的 bug：
// 浏览器把 textarea 的 LF 换成 CRLF 提交，私钥留空（保留现有私钥），
// 证书内容未变，此时保存不应报“必须同时填写”。
func TestHandleDomainTLSUpdateUnchangedCertCRLF(t *testing.T) {
	certLF, keyLF := makeTestCertPair(t, "lf")
	h := &AdminHandler{tlsDir: t.TempDir()}
	domain := writeExistingCert(t, h.tlsDir, certLF, keyLF)

	submittedCert := strings.ReplaceAll(certLF, "\n", "\r\n") // 模拟浏览器提交
	if err := h.handleDomainTLSUpdate(domain, submittedCert, ""); err != nil {
		t.Fatalf("证书未修改且私钥留空时应保留现有私钥，实际报错: %v", err)
	}
}

// TestHandleDomainTLSUpdateNewPairCRLF 新证书+私钥（CRLF 提交）应正常保存，
// 且写入文件为 LF 换行、能组成有效密钥对。
func TestHandleDomainTLSUpdateNewPairCRLF(t *testing.T) {
	certCRLF, keyCRLF := makeTestCertPair(t, "crlf")
	h := &AdminHandler{tlsDir: t.TempDir()}
	domain := &db.Domain{ID: 1, Name: "mail.example.com", TlsEnabled: true}

	if err := h.handleDomainTLSUpdate(domain, certCRLF, keyCRLF); err != nil {
		t.Fatalf("CRLF 提交的新证书应保存成功，实际报错: %v", err)
	}

	data, err := os.ReadFile(domain.TlsCertPath)
	if err != nil {
		t.Fatalf("读取保存的证书失败: %v", err)
	}
	if strings.Contains(string(data), "\r") {
		t.Error("保存的证书文件不应包含 CR")
	}
	if _, err := tls.LoadX509KeyPair(domain.TlsCertPath, domain.TlsKeyPath); err != nil {
		t.Fatalf("保存的证书对无效: %v", err)
	}
}

// TestHandleDomainTLSUpdateChangedCertWithoutKey 证书确实被修改但私钥留空，
// 应报“必须同时填写”（防止用不匹配的私钥）。
func TestHandleDomainTLSUpdateChangedCertWithoutKey(t *testing.T) {
	certA, keyA := makeTestCertPair(t, "lf")
	certB, _ := makeTestCertPair(t, "lf")
	h := &AdminHandler{tlsDir: t.TempDir()}
	domain := writeExistingCert(t, h.tlsDir, certA, keyA)

	err := h.handleDomainTLSUpdate(domain, certB, "")
	if err == nil || !strings.Contains(err.Error(), "必须同时填写") {
		t.Fatalf("修改证书但私钥留空应报“必须同时填写”，实际: %v", err)
	}
}
