package api

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var validDomainRE = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`)

const (
	certDir  = "/etc/ssl/archivmail"
	certPath = "/etc/ssl/archivmail/archivmail.crt"
	keyPath  = "/etc/ssl/archivmail/archivmail.key"
)

// certInfoResponse holds the parsed certificate metadata returned by GET /api/admin/cert/info.
type certInfoResponse struct {
	Exists            bool      `json:"exists"`
	Subject           string    `json:"subject,omitempty"`
	Issuer            string    `json:"issuer,omitempty"`
	NotBefore         time.Time `json:"not_before,omitempty"`
	NotAfter          time.Time `json:"not_after,omitempty"`
	DNSNames          []string  `json:"dns_names,omitempty"`
	IPAddresses       []string  `json:"ip_addresses,omitempty"`
	FingerprintSHA256 string    `json:"fingerprint_sha256,omitempty"`
	IsSelfSigned      bool      `json:"is_self_signed,omitempty"`
	DaysRemaining     int       `json:"days_remaining,omitempty"`
}

// handleCertInfo returns metadata about the currently installed TLS certificate.
// GET /api/admin/cert/info
func (s *Server) handleCertInfo(w http.ResponseWriter, r *http.Request) {
	info, err := readCertInfo()
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to read certificate: %v", err))
		return
	}
	writeJSON(w, http.StatusOK, info)
}

// readCertInfo reads and parses the certificate file at certPath.
// Returns {"exists": false} when the file is absent; an error only on read/parse failures.
func readCertInfo() (*certInfoResponse, error) {
	raw, err := os.ReadFile(certPath)
	if os.IsNotExist(err) {
		return &certInfoResponse{Exists: false}, nil
	}
	if err != nil {
		return nil, fmt.Errorf("read cert file: %w", err)
	}

	block, _ := pem.Decode(raw)
	if block == nil {
		return nil, fmt.Errorf("no PEM block found in certificate file")
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("parse certificate: %w", err)
	}

	ips := make([]string, 0, len(cert.IPAddresses))
	for _, ip := range cert.IPAddresses {
		ips = append(ips, ip.String())
	}

	sum := sha256.Sum256(cert.Raw)
	parts := make([]string, 32)
	for i, b := range sum {
		parts[i] = fmt.Sprintf("%02X", b)
	}
	fingerprint := strings.Join(parts, ":")

	isSelfSigned := cert.Subject.String() == cert.Issuer.String()
	daysRemaining := int(time.Until(cert.NotAfter).Hours() / 24)

	dnsNames := cert.DNSNames
	if dnsNames == nil {
		dnsNames = []string{}
	}
	if ips == nil {
		ips = []string{}
	}

	return &certInfoResponse{
		Exists:            true,
		Subject:           cert.Subject.String(),
		Issuer:            cert.Issuer.String(),
		NotBefore:         cert.NotBefore,
		NotAfter:          cert.NotAfter,
		DNSNames:          dnsNames,
		IPAddresses:       ips,
		FingerprintSHA256: fingerprint,
		IsSelfSigned:      isSelfSigned,
		DaysRemaining:     daysRemaining,
	}, nil
}

// handleCertUpload validates and installs an uploaded PEM certificate + key pair.
// POST /api/admin/cert/upload  (multipart/form-data fields: cert, key)
func (s *Server) handleCertUpload(w http.ResponseWriter, r *http.Request) {
	// Limit to 4 MB to prevent abuse; real PEM files are a few KB at most.
	if err := r.ParseMultipartForm(4 << 20); err != nil {
		writeError(w, http.StatusBadRequest, "failed to parse multipart form")
		return
	}

	certPEM, err := readMultipartField(r, "cert")
	if err != nil {
		writeError(w, http.StatusBadRequest, "missing or unreadable 'cert' field")
		return
	}
	keyPEM, err := readMultipartField(r, "key")
	if err != nil {
		writeError(w, http.StatusBadRequest, "missing or unreadable 'key' field")
		return
	}

	// Validate that cert and key form a valid pair before touching disk.
	if _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil {
		writeError(w, http.StatusBadRequest, fmt.Sprintf("certificate and key do not match: %v", err))
		return
	}

	if err := writeCertFiles(certPEM, keyPEM); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to write certificate files: %v", err))
		return
	}

	if out, err := reloadNginx(); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("nginx reload failed: %v — output: %s", err, out))
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"ok":      true,
		"message": "Zertifikat gespeichert und nginx neu geladen",
	})
}

// handleCertSelfSigned generates a new self-signed RSA-4096 certificate using Go's crypto/x509.
// POST /api/admin/cert/self-signed
func (s *Server) handleCertSelfSigned(w http.ResponseWriter, r *http.Request) {
	var req struct {
		CommonName    string   `json:"common_name"`
		DNSNames      []string `json:"dns_names"`
		IPAddresses   []string `json:"ip_addresses"`
		ValidityYears int      `json:"validity_years"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}
	if req.CommonName == "" {
		writeError(w, http.StatusBadRequest, "common_name is required")
		return
	}
	if req.ValidityYears <= 0 || req.ValidityYears > 30 {
		req.ValidityYears = 10
	}

	// Parse and validate IP addresses.
	ips := make([]net.IP, 0, len(req.IPAddresses))
	for _, addr := range req.IPAddresses {
		ip := net.ParseIP(addr)
		if ip == nil {
			writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid IP address: %s", addr))
			return
		}
		ips = append(ips, ip)
	}

	// Generate RSA-4096 private key.
	privKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("key generation failed: %v", err))
		return
	}

	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		writeError(w, http.StatusInternalServerError, "serial number generation failed")
		return
	}

	notBefore := time.Now().UTC()
	notAfter := notBefore.AddDate(req.ValidityYears, 0, 0)

	dnsNames := req.DNSNames
	if dnsNames == nil {
		dnsNames = []string{}
	}

	tmpl := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			CommonName: req.CommonName,
		},
		DNSNames:              dnsNames,
		IPAddresses:           ips,
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IsCA:                  true,
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privKey.PublicKey, privKey)
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("certificate creation failed: %v", err))
		return
	}

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)})

	if err := writeCertFiles(certPEM, keyPEM); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to write certificate files: %v", err))
		return
	}

	if out, err := reloadNginx(); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("nginx reload failed: %v — output: %s", err, out))
		return
	}

	info, err := readCertInfo()
	if err != nil {
		writeError(w, http.StatusInternalServerError, "certificate written but could not read back info")
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"ok":   true,
		"cert": info,
	})
}

// handleCertACME runs certbot to obtain a Let's Encrypt certificate via the NGINX plugin.
// POST /api/admin/cert/acme
func (s *Server) handleCertACME(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Domain string `json:"domain"`
		Email  string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}
	if req.Domain == "" {
		writeError(w, http.StatusBadRequest, "domain is required")
		return
	}
	if req.Email == "" {
		writeError(w, http.StatusBadRequest, "email is required")
		return
	}
	if !validDomainRE.MatchString(req.Domain) {
		writeError(w, http.StatusBadRequest, "invalid domain name")
		return
	}

	// Verify certbot is available before attempting anything.
	certbotPath, err := exec.LookPath("certbot")
	if err != nil {
		writeError(w, http.StatusInternalServerError, "certbot is not installed on this server")
		return
	}

	cmd := exec.Command(certbotPath,
		"certonly", "--nginx",
		"-d", req.Domain,
		"--non-interactive",
		"--agree-tos",
		"-m", req.Email,
	)
	out, err := cmd.CombinedOutput()
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("certbot failed: %v\n%s", err, string(out)))
		return
	}

	// Copy the issued certificates from /etc/letsencrypt/live/<domain>/ to certDir.
	lePath := filepath.Join("/etc/letsencrypt/live", req.Domain)
	certPEM, err := os.ReadFile(filepath.Join(lePath, "fullchain.pem"))
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("certbot succeeded but could not read issued cert: %v", err))
		return
	}
	keyPEM, err := os.ReadFile(filepath.Join(lePath, "privkey.pem"))
	if err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("certbot succeeded but could not read issued key: %v", err))
		return
	}

	if err := writeCertFiles(certPEM, keyPEM); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to copy certificates: %v", err))
		return
	}

	if nginxOut, err := reloadNginx(); err != nil {
		writeError(w, http.StatusInternalServerError, fmt.Sprintf("nginx reload failed: %v — output: %s", err, nginxOut))
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"ok":     true,
		"output": string(out),
	})
}

// --- internal helpers ---

// writeCertFiles creates certDir if necessary and writes cert (0644) + key (0600).
func writeCertFiles(certPEM, keyPEM []byte) error {
	if err := os.MkdirAll(certDir, 0755); err != nil {
		return fmt.Errorf("create cert dir: %w", err)
	}
	if err := os.WriteFile(certPath, certPEM, 0644); err != nil {
		return fmt.Errorf("write cert: %w", err)
	}
	if err := os.WriteFile(keyPath, keyPEM, 0600); err != nil {
		return fmt.Errorf("write key: %w", err)
	}
	return nil
}

// reloadNginx tests nginx config validity, then reloads the service.
// Returns combined stdout+stderr output and any error.
func reloadNginx() (string, error) {
	testCmd := exec.Command("nginx", "-t")
	testOut, err := testCmd.CombinedOutput()
	if err != nil {
		return string(testOut), fmt.Errorf("nginx config test failed: %w", err)
	}

	reloadCmd := exec.Command("systemctl", "reload", "nginx")
	reloadOut, err := reloadCmd.CombinedOutput()
	if err != nil {
		return string(reloadOut), fmt.Errorf("systemctl reload nginx: %w", err)
	}

	return string(testOut) + string(reloadOut), nil
}

// readMultipartField reads all bytes from a named file field in a parsed multipart form.
func readMultipartField(r *http.Request, field string) ([]byte, error) {
	f, _, err := r.FormFile(field)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return io.ReadAll(f)
}
