// Package digicert implements the issuer.Connector interface for DigiCert CertCentral.
//
// DigiCert CertCentral is an enterprise certificate authority offering DV, OV, and EV
// certificates. Unlike synchronous issuers (Vault, step-ca), DigiCert uses an
// asynchronous order model: submit an order, receive an order ID, then poll for
// completion. OV/EV certificates require organization validation which may take hours
// or days; DV certificates may be issued immediately.
//
// This connector maps to certctl's existing job state machine:
//   - IssueCertificate submits the order; if status is "issued", returns cert immediately.
//     If status is "pending", returns OrderID with empty CertPEM — the job system polls
//     via GetOrderStatus.
//   - GetOrderStatus polls the order; when status becomes "issued", downloads and
//     parses the PEM bundle.
//
// Authentication: API key via X-DC-DEVKEY header.
//
// DigiCert CertCentral API used:
//
//	POST /order/certificate/{product_type}          - Submit certificate order
//	GET  /order/certificate/{order_id}              - Check order status
//	GET  /certificate/{certificate_id}/download/format/pem_all - Download cert bundle
//	PUT  /certificate/{certificate_id}/revoke       - Revoke certificate
//	GET  /user/me                                   - Validate API credentials
package digicert

import (
	"bytes"
	"context"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/shankar0123/certctl/internal/connector/issuer"
)

// Config represents the DigiCert CertCentral issuer connector configuration.
type Config struct {
	// APIKey is the CertCentral API key for authentication.
	// Required. Set via CERTCTL_DIGICERT_API_KEY environment variable.
	APIKey string `json:"api_key"`

	// OrgID is the DigiCert organization ID for certificate orders.
	// Required. Set via CERTCTL_DIGICERT_ORG_ID environment variable.
	OrgID string `json:"org_id"`

	// ProductType is the DigiCert product type for certificate orders.
	// Default: "ssl_basic". Set via CERTCTL_DIGICERT_PRODUCT_TYPE environment variable.
	// Common values: "ssl_basic", "ssl_wildcard", "ssl_ev_basic", "ssl_plus", "ssl_multi_domain".
	ProductType string `json:"product_type"`

	// BaseURL is the DigiCert CertCentral API base URL.
	// Default: "https://www.digicert.com/services/v2".
	// Set via CERTCTL_DIGICERT_BASE_URL environment variable.
	BaseURL string `json:"base_url"`
}

// Connector implements the issuer.Connector interface for DigiCert CertCentral.
type Connector struct {
	config     *Config
	logger     *slog.Logger
	httpClient *http.Client
}

// New creates a new DigiCert CertCentral connector with the given configuration and logger.
func New(config *Config, logger *slog.Logger) *Connector {
	if config != nil {
		if config.ProductType == "" {
			config.ProductType = "ssl_basic"
		}
		if config.BaseURL == "" {
			config.BaseURL = "https://www.digicert.com/services/v2"
		}
	}

	return &Connector{
		config: config,
		logger: logger,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// orderRequest is the JSON body for DigiCert certificate order submission.
type orderRequest struct {
	Certificate   orderCert   `json:"certificate"`
	Organization  orderOrg    `json:"organization"`
	ValidityYears int         `json:"validity_years"`
}

type orderCert struct {
	CommonName string   `json:"common_name"`
	CSR        string   `json:"csr"`
	DNSNames   []string `json:"dns_names,omitempty"`
}

type orderOrg struct {
	ID json.Number `json:"id"`
}

// orderResponse is the JSON response from a certificate order submission.
type orderResponse struct {
	ID            int    `json:"id"`
	Status        string `json:"status"`
	CertificateID int    `json:"certificate_id,omitempty"`
}

// orderStatusResponse is the JSON response from an order status check.
type orderStatusResponse struct {
	ID          int    `json:"id"`
	Status      string `json:"status"`
	Certificate struct {
		ID         int    `json:"id"`
		CommonName string `json:"common_name"`
	} `json:"certificate"`
}

// ValidateConfig checks that the DigiCert configuration is valid and API access works.
func (c *Connector) ValidateConfig(ctx context.Context, rawConfig json.RawMessage) error {
	var cfg Config
	if err := json.Unmarshal(rawConfig, &cfg); err != nil {
		return fmt.Errorf("invalid DigiCert config: %w", err)
	}

	if cfg.APIKey == "" {
		return fmt.Errorf("DigiCert api_key is required")
	}

	if cfg.OrgID == "" {
		return fmt.Errorf("DigiCert org_id is required")
	}

	if cfg.ProductType == "" {
		cfg.ProductType = "ssl_basic"
	}
	if cfg.BaseURL == "" {
		cfg.BaseURL = "https://www.digicert.com/services/v2"
	}

	// Test API access via /user/me
	meURL := cfg.BaseURL + "/user/me"
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, meURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create API test request: %w", err)
	}
	req.Header.Set("X-DC-DEVKEY", cfg.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("DigiCert API not reachable at %s: %w", cfg.BaseURL, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized {
		return fmt.Errorf("DigiCert API key is invalid (status %d)", resp.StatusCode)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("DigiCert API returned status %d", resp.StatusCode)
	}

	c.config = &cfg
	c.logger.Info("DigiCert CertCentral configuration validated",
		"base_url", cfg.BaseURL,
		"product_type", cfg.ProductType)

	return nil
}

// IssueCertificate submits a certificate order to DigiCert CertCentral.
// If the certificate is issued immediately (DV certs), returns the cert.
// If pending (OV/EV certs), returns OrderID with empty CertPEM for polling.
func (c *Connector) IssueCertificate(ctx context.Context, request issuer.IssuanceRequest) (*issuer.IssuanceResult, error) {
	c.logger.Info("processing DigiCert issuance request",
		"common_name", request.CommonName,
		"san_count", len(request.SANs),
		"product_type", c.config.ProductType)

	orderReq := orderRequest{
		Certificate: orderCert{
			CommonName: request.CommonName,
			CSR:        request.CSRPEM,
			DNSNames:   request.SANs,
		},
		Organization: orderOrg{
			ID: json.Number(c.config.OrgID),
		},
		ValidityYears: 1,
	}

	body, err := json.Marshal(orderReq)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal order request: %w", err)
	}

	orderURL := fmt.Sprintf("%s/order/certificate/%s", c.config.BaseURL, c.config.ProductType)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, orderURL, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("failed to create order request: %w", err)
	}
	req.Header.Set("X-DC-DEVKEY", c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("DigiCert order request failed: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read order response: %w", err)
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("DigiCert order returned status %d: %s", resp.StatusCode, string(respBody))
	}

	var orderResp orderResponse
	if err := json.Unmarshal(respBody, &orderResp); err != nil {
		return nil, fmt.Errorf("failed to parse order response: %w", err)
	}

	orderID := fmt.Sprintf("%d", orderResp.ID)

	c.logger.Info("DigiCert order submitted",
		"order_id", orderID,
		"status", orderResp.Status)

	// If issued immediately (DV certs), download the certificate
	if orderResp.Status == "issued" && orderResp.CertificateID > 0 {
		certPEM, chainPEM, serial, notBefore, notAfter, err := c.downloadCertificate(ctx, orderResp.CertificateID)
		if err != nil {
			return nil, fmt.Errorf("failed to download certificate: %w", err)
		}

		c.logger.Info("DigiCert certificate issued immediately",
			"order_id", orderID,
			"serial", serial)

		return &issuer.IssuanceResult{
			CertPEM:   certPEM,
			ChainPEM:  chainPEM,
			Serial:    serial,
			NotBefore: notBefore,
			NotAfter:  notAfter,
			OrderID:   orderID,
		}, nil
	}

	// Pending — return OrderID for polling via GetOrderStatus
	c.logger.Info("DigiCert order pending validation",
		"order_id", orderID,
		"status", orderResp.Status)

	return &issuer.IssuanceResult{
		OrderID: orderID,
	}, nil
}

// RenewCertificate renews a certificate by submitting a new order.
// DigiCert uses reissue for renewal, but for simplicity we submit a new order
// (reissue requires the original order ID which may not be available).
func (c *Connector) RenewCertificate(ctx context.Context, request issuer.RenewalRequest) (*issuer.IssuanceResult, error) {
	c.logger.Info("processing DigiCert renewal request",
		"common_name", request.CommonName,
		"san_count", len(request.SANs))

	return c.IssueCertificate(ctx, issuer.IssuanceRequest{
		CommonName: request.CommonName,
		SANs:       request.SANs,
		CSRPEM:     request.CSRPEM,
		EKUs:       request.EKUs,
	})
}

// RevokeCertificate revokes a certificate at DigiCert CertCentral.
// DigiCert revocation uses certificate_id, so we extract it from the serial
// by looking up the order. For simplicity, we use the serial as the cert ID
// (the caller should provide the DigiCert certificate ID).
func (c *Connector) RevokeCertificate(ctx context.Context, request issuer.RevocationRequest) error {
	c.logger.Info("processing DigiCert revocation request", "serial", request.Serial)

	reason := "unspecified"
	if request.Reason != nil {
		reason = *request.Reason
	}

	revokeBody := map[string]interface{}{
		"reason": reason,
	}

	body, err := json.Marshal(revokeBody)
	if err != nil {
		return fmt.Errorf("failed to marshal revoke request: %w", err)
	}

	// DigiCert uses certificate_id in the URL path for revocation
	revokeURL := fmt.Sprintf("%s/certificate/%s/revoke", c.config.BaseURL, request.Serial)
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, revokeURL, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("failed to create revoke request: %w", err)
	}
	req.Header.Set("X-DC-DEVKEY", c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("DigiCert revoke request failed: %w", err)
	}
	defer resp.Body.Close()

	// DigiCert returns 204 No Content on successful revocation
	if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("DigiCert revoke returned status %d: %s", resp.StatusCode, string(respBody))
	}

	c.logger.Info("DigiCert certificate revoked", "serial", request.Serial, "reason", reason)
	return nil
}

// GetOrderStatus checks the status of a DigiCert certificate order.
// If the order is "issued", downloads the certificate and returns it.
// If still "pending", returns pending status for continued polling.
func (c *Connector) GetOrderStatus(ctx context.Context, orderID string) (*issuer.OrderStatus, error) {
	c.logger.Debug("checking DigiCert order status", "order_id", orderID)

	statusURL := fmt.Sprintf("%s/order/certificate/%s", c.config.BaseURL, orderID)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, statusURL, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create status request: %w", err)
	}
	req.Header.Set("X-DC-DEVKEY", c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("DigiCert status request failed: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read status response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("DigiCert order status returned %d: %s", resp.StatusCode, string(respBody))
	}

	var statusResp orderStatusResponse
	if err := json.Unmarshal(respBody, &statusResp); err != nil {
		return nil, fmt.Errorf("failed to parse status response: %w", err)
	}

	now := time.Now()

	switch statusResp.Status {
	case "issued":
		if statusResp.Certificate.ID == 0 {
			return nil, fmt.Errorf("order is issued but certificate_id is missing")
		}

		certPEM, chainPEM, serial, notBefore, notAfter, err := c.downloadCertificate(ctx, statusResp.Certificate.ID)
		if err != nil {
			return nil, fmt.Errorf("failed to download certificate: %w", err)
		}

		c.logger.Info("DigiCert order completed",
			"order_id", orderID,
			"serial", serial)

		return &issuer.OrderStatus{
			OrderID:   orderID,
			Status:    "completed",
			CertPEM:   &certPEM,
			ChainPEM:  &chainPEM,
			Serial:    &serial,
			NotBefore: &notBefore,
			NotAfter:  &notAfter,
			UpdatedAt: now,
		}, nil

	case "pending", "processing":
		msg := fmt.Sprintf("order %s is %s", orderID, statusResp.Status)
		return &issuer.OrderStatus{
			OrderID:   orderID,
			Status:    "pending",
			Message:   &msg,
			UpdatedAt: now,
		}, nil

	case "rejected", "denied":
		msg := fmt.Sprintf("order %s was %s", orderID, statusResp.Status)
		return &issuer.OrderStatus{
			OrderID:   orderID,
			Status:    "failed",
			Message:   &msg,
			UpdatedAt: now,
		}, nil

	default:
		msg := fmt.Sprintf("unknown order status: %s", statusResp.Status)
		return &issuer.OrderStatus{
			OrderID:   orderID,
			Status:    "pending",
			Message:   &msg,
			UpdatedAt: now,
		}, nil
	}
}

// downloadCertificate downloads the PEM bundle for a DigiCert certificate.
func (c *Connector) downloadCertificate(ctx context.Context, certificateID int) (certPEM string, chainPEM string, serial string, notBefore time.Time, notAfter time.Time, err error) {
	downloadURL := fmt.Sprintf("%s/certificate/%d/download/format/pem_all", c.config.BaseURL, certificateID)
	req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
	if reqErr != nil {
		err = fmt.Errorf("failed to create download request: %w", reqErr)
		return
	}
	req.Header.Set("X-DC-DEVKEY", c.config.APIKey)

	resp, doErr := c.httpClient.Do(req)
	if doErr != nil {
		err = fmt.Errorf("DigiCert download request failed: %w", doErr)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		err = fmt.Errorf("DigiCert download returned status %d: %s", resp.StatusCode, string(body))
		return
	}

	body, readErr := io.ReadAll(resp.Body)
	if readErr != nil {
		err = fmt.Errorf("failed to read download response: %w", readErr)
		return
	}

	// Parse the PEM bundle: first cert is the leaf, rest are intermediates
	certPEM, chainPEM, serial, notBefore, notAfter, err = parsePEMBundle(string(body))
	return
}

// parsePEMBundle splits a PEM bundle into leaf cert and chain, extracting metadata.
func parsePEMBundle(bundle string) (certPEM string, chainPEM string, serial string, notBefore time.Time, notAfter time.Time, err error) {
	var certs []string
	remaining := bundle

	for {
		var block *pem.Block
		block, rest := pem.Decode([]byte(remaining))
		if block == nil {
			break
		}
		if block.Type == "CERTIFICATE" {
			certs = append(certs, string(pem.EncodeToMemory(block)))
		}
		remaining = string(rest)
	}

	if len(certs) == 0 {
		err = fmt.Errorf("no certificates found in PEM bundle")
		return
	}

	certPEM = certs[0]
	if len(certs) > 1 {
		chainPEM = strings.Join(certs[1:], "")
	}

	// Parse leaf cert for metadata
	block, _ := pem.Decode([]byte(certPEM))
	if block == nil {
		err = fmt.Errorf("failed to decode leaf certificate PEM")
		return
	}

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

	serial = cert.SerialNumber.String()
	notBefore = cert.NotBefore
	notAfter = cert.NotAfter
	return
}

// GenerateCRL is not supported because DigiCert manages CRL distribution.
func (c *Connector) GenerateCRL(ctx context.Context, revokedCerts []issuer.RevokedCertEntry) ([]byte, error) {
	return nil, fmt.Errorf("DigiCert manages CRL distribution; use DigiCert's CRL endpoints")
}

// SignOCSPResponse is not supported because DigiCert manages OCSP.
func (c *Connector) SignOCSPResponse(ctx context.Context, req issuer.OCSPSignRequest) ([]byte, error) {
	return nil, fmt.Errorf("DigiCert manages OCSP; use DigiCert's OCSP responder")
}

// GetCACertPEM is not directly supported. DigiCert intermediate certificates
// come with each certificate issuance as part of the PEM bundle.
func (c *Connector) GetCACertPEM(ctx context.Context) (string, error) {
	return "", fmt.Errorf("DigiCert intermediate certificates are included with each issued certificate")
}

// GetRenewalInfo returns nil, nil as DigiCert does not support ACME Renewal Information (ARI).
func (c *Connector) GetRenewalInfo(ctx context.Context, certPEM string) (*issuer.RenewalInfoResult, error) {
	return nil, nil
}

// Ensure Connector implements the issuer.Connector interface.
var _ issuer.Connector = (*Connector)(nil)
