package regfishapi

import "context"

// TLS certificate product SKUs.
const (
	TLSProductRapidSSL         = "RapidSSL"
	TLSProductRapidSSLWildcard = "RapidSSLWildcard"
	TLSProductQuickSSLPremium  = "QuickSSLPremium"
	TLSProductSSL123           = "SSL123"
	TLSProductTrueBusinessID   = "TrueBusinessID"
	TLSProductSecureSite       = "SecureSite"
	TLSProductSSLWebServer     = "SSLWebServer"
)

// Domain control validation methods.
const (
	DCVMethodDNSCNAMEToken = "dns-cname-token"
	DCVMethodEmail         = "email"
)

// TLS certificate lifecycle states.
const (
	TLSStatusPending                = "pending"
	TLSStatusIssued                 = "issued"
	TLSStatusPendingCancellation    = "pending_cancellation"
	TLSStatusPendingRevocation      = "pending_revocation"
	TLSStatusPendingOrderRevocation = "pending_order_revocation"
	TLSStatusCancelled              = "cancelled"
	TLSStatusOrderCancelled         = "order_cancelled"
	TLSStatusRevoked                = "revoked"
	TLSStatusOrderRevoked           = "order_revoked"
	TLSStatusRejected               = "rejected"
	TLSStatusExpired                = "expired"
	TLSStatusUnknown                = "unknown"
)

// Revocation reasons forwarded to the CA.
const (
	RevocationReasonUnspecified          = "unspecified"
	RevocationReasonKeyCompromise        = "keyCompromise"
	RevocationReasonAffiliationChanged   = "affiliationChanged"
	RevocationReasonSuperseded           = "superseded"
	RevocationReasonCessationOfOperation = "cessationOfOperation"
)

// Certificate download formats.
const (
	TLSDownloadFormatPEM = "pem"
	TLSDownloadFormatZIP = "zip"
)

// TLS organization readiness.
const (
	TLSOrganizationStatusReady      = "ready"
	TLSOrganizationStatusIncomplete = "incomplete"
)

// TLSValidationDNSRecord is a DNS record that has to be published to complete
// domain control validation.
type TLSValidationDNSRecord struct {
	Name  string `json:"name"`
	Type  string `json:"type"`
	Value string `json:"value"`
}

// TLSValidation describes how a certificate order is validated.
type TLSValidation struct {
	// Method is DCVMethodDNSCNAMEToken or DCVMethodEmail.
	Method       string                   `json:"method"`
	DNSRecords   []TLSValidationDNSRecord `json:"dns_records,omitempty"`
	EmailTargets []string                 `json:"email_targets,omitempty"`
}

// TLSCertificateOrganization is the organization a certificate is bound to.
type TLSCertificateOrganization struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Status string `json:"status"`
	// UsableForOrdering reports whether the organization can back a new order.
	UsableForOrdering bool `json:"usable_for_ordering"`
}

// TLSReissue is the state of a certificate reissue.
type TLSReissue struct {
	ID            int            `json:"id"`
	Status        string         `json:"status"`
	CommonName    string         `json:"common_name"`
	DNSNames      []string       `json:"dns_names"`
	OrderState    string         `json:"order_state,omitempty"`
	RequestedAt   string         `json:"requested_at,omitempty"`
	LastUpdatedAt string         `json:"last_updated_at,omitempty"`
	Validation    *TLSValidation `json:"validation,omitempty"`
}

// TLSCertificate is a TLS certificate order and, once issued, the certificate
// itself.
type TLSCertificate struct {
	ID string `json:"id"`
	// Status is one of the TLSStatus* constants.
	Status     string   `json:"status"`
	CommonName string   `json:"common_name"`
	Product    string   `json:"product"`
	Provider   string   `json:"provider"`
	DNSNames   []string `json:"dns_names"`
	// ActionRequired reports whether the order needs further input before it
	// can be submitted; CompletionURL and CompleteCertificate lead there.
	ActionRequired         bool                        `json:"action_required"`
	PendingReason          string                      `json:"pending_reason"`
	PendingMessage         string                      `json:"pending_message"`
	CompletionURL          string                      `json:"completion_url"`
	OrganizationID         *string                     `json:"organization_id"`
	Organization           *TLSCertificateOrganization `json:"organization,omitempty"`
	OrderState             string                      `json:"order_state,omitempty"`
	RevocationScope        string                      `json:"revocation_scope,omitempty"`
	RevocationPendingScope string                      `json:"revocation_pending_scope,omitempty"`
	ReissueSupported       bool                        `json:"reissue_supported,omitempty"`
	// ValidityDays is the purchased base order validity. On provider-linked
	// renewals the issued lifetime can be longer — ValidFrom and ValidUntil
	// are authoritative.
	ValidityDays            int            `json:"validity_days,omitempty"`
	RenewalBonusDays        int            `json:"renewal_bonus_days,omitempty"`
	SerialNumber            string         `json:"serial_number,omitempty"`
	ValidFrom               string         `json:"valid_from,omitempty"`
	ValidUntil              string         `json:"valid_until,omitempty"`
	ContractValidFrom       string         `json:"contract_valid_from,omitempty"`
	ContractValidUntil      string         `json:"contract_valid_until,omitempty"`
	LastStatusCheck         string         `json:"last_status_check,omitempty"`
	CertificatePEMAvailable bool           `json:"certificate_pem_available"`
	OrderCancellable        bool           `json:"order_cancellable,omitempty"`
	OrderCancellationMode   string         `json:"order_cancellation_mode,omitempty"`
	OrderCancellableUntil   string         `json:"order_cancellable_until,omitempty"`
	Validation              *TLSValidation `json:"validation,omitempty"`
	Reissue                 *TLSReissue    `json:"reissue,omitempty"`
}

// TLSProductSpecs are the capabilities of a certificate product.
type TLSProductSpecs struct {
	Seal         bool `json:"seal"`
	SANSupport   bool `json:"san_support"`
	WWWOnApex    bool `json:"www_on_apex"`
	ApexOnWC     bool `json:"apex_on_wc"`
	WCSupport    bool `json:"wc_support"`
	SANWCSupport bool `json:"san_wc_support"`
	MaxSANs      int  `json:"max_sans"`
	MalwareCheck bool `json:"malware_check"`
}

// TLSProductPrice is the account-specific price of a certificate product.
type TLSProductPrice struct {
	Yearly float64 `json:"yearly"`
	Daily  float64 `json:"daily"`
}

// TLSProduct is an orderable certificate product.
type TLSProduct struct {
	SKU  string `json:"sku"`
	Name string `json:"name"`
	// Type is "DV", "OV" or "EV".
	Type string `json:"type"`
	// ValidationLevel is the lower-case form of Type.
	ValidationLevel      string          `json:"validation_level"`
	OrganizationRequired bool            `json:"organization_required"`
	CA                   string          `json:"ca"`
	Price                TLSProductPrice `json:"price"`
	Recommended          bool            `json:"recommended,omitempty"`
	Specs                TLSProductSpecs `json:"specs"`
}

// TLSOrganization is an organization usable for OV and EV certificate orders.
type TLSOrganization struct {
	ID           string `json:"id"`
	Organization string `json:"organization"`
	// Status is TLSOrganizationStatusReady or TLSOrganizationStatusIncomplete.
	Status               string `json:"status"`
	UsableForOrdering    bool   `json:"usable_for_ordering"`
	Provider             string `json:"provider,omitempty"`
	ProviderStatus       string `json:"provider_status,omitempty"`
	ValidationStatus     string `json:"validation_status,omitempty"`
	ValidationType       string `json:"validation_type,omitempty"`
	ValidatedUntil       string `json:"validated_until,omitempty"`
	RevalidationRequired bool   `json:"revalidation_required,omitempty"`
	FirstName            string `json:"first_name"`
	LastName             string `json:"last_name"`
	Address              string `json:"address"`
	PostalCode           string `json:"postal_code"`
	City                 string `json:"city"`
	CountryCode          string `json:"country_code"`
	Phone                string `json:"phone"`
	Email                string `json:"email"`
}

// TLSCertificateRequest is the payload of CreateCertificate.
type TLSCertificateRequest struct {
	// SKU is one of the TLSProduct* constants.
	SKU        string   `json:"sku"`
	CommonName string   `json:"common_name"`
	DNSNames   []string `json:"dns_names,omitempty"`
	// CSR is the PEM encoded certificate signing request.
	CSR string `json:"csr"`
	// DCVMethod is DCVMethodDNSCNAMEToken or DCVMethodEmail.
	DCVMethod string   `json:"dcv_method"`
	DCVEmails []string `json:"dcv_emails,omitempty"`
	// OrgID references a TLS organization for OV and EV orders.
	OrgID string `json:"org_id,omitempty"`
	// RenewalOfCertificateID submits the order as a provider-linked renewal of
	// the referenced certificate.
	RenewalOfCertificateID string `json:"renewal_of_certificate_id,omitempty"`
	// ValidityDays is the purchased base order validity, 1–199. Zero leaves
	// the choice to the API (199).
	ValidityDays int `json:"validity_days,omitempty"`
}

// TLSCertificateCancelRequest is the optional payload of CancelCertificate.
type TLSCertificateCancelRequest struct {
	// Note is stored with the cancellation request at the provider. regfish
	// sends a default note when it is empty, because the CA requires one.
	Note string `json:"note,omitempty"`
}

// TLSCertificateRevokeRequest is the optional payload of RevokeCertificate.
type TLSCertificateRevokeRequest struct {
	Comment string `json:"comment,omitempty"`
	// RevocationReason is one of the RevocationReason* constants and defaults
	// to RevocationReasonUnspecified.
	RevocationReason string `json:"revocation_reason,omitempty"`
}

// TLSCertificateOrderCancelRequest is the optional payload of
// CancelIssuedCertificateOrder.
type TLSCertificateOrderCancelRequest struct {
	Comment          string `json:"comment,omitempty"`
	RevocationReason string `json:"revocation_reason,omitempty"`
}

// TLSCertificateReissueRequest is the payload of ReissueCertificate.
type TLSCertificateReissueRequest struct {
	CSR string `json:"csr"`
	// CommonName is optional; when set it must match the current order exactly.
	CommonName string `json:"common_name,omitempty"`
	// DNSNames is optional; when set the SAN list must match the current order
	// exactly.
	DNSNames  []string `json:"dns_names,omitempty"`
	DCVMethod string   `json:"dcv_method"`
	Comments  string   `json:"comments,omitempty"`
	// ValidityDays optionally requests a shorter replacement lifetime. The CA
	// honors it only for eligible multi-year plan orders.
	ValidityDays int `json:"validity_days,omitempty"`
}

// TLSOrganizationCreateRequest is the payload of CreateOrganization.
type TLSOrganizationCreateRequest struct {
	Organization string `json:"organization"`
	FirstName    string `json:"first_name"`
	LastName     string `json:"last_name"`
	Address      string `json:"address"`
	PostalCode   string `json:"postal_code"`
	City         string `json:"city"`
	// CountryCode is a two letter ISO country code.
	CountryCode string `json:"country_code"`
	Phone       string `json:"phone"`
	Email       string `json:"email"`
}

// Edit modes for TLSOrganizationPatchRequest.
const (
	// TLSOrgEditModeCreateReplacement creates a replacement organization when
	// validation-relevant fields change.
	TLSOrgEditModeCreateReplacement = "create_replacement"
	// TLSOrgEditModeUpdateProvider updates the existing provider record.
	TLSOrgEditModeUpdateProvider = "update_provider"
)

// TLSOrganizationPatchRequest is the payload of PatchOrganizationByID. At
// least one field must be set.
type TLSOrganizationPatchRequest struct {
	// EditMode is TLSOrgEditModeCreateReplacement or
	// TLSOrgEditModeUpdateProvider.
	EditMode     string `json:"edit_mode,omitempty"`
	Organization string `json:"organization,omitempty"`
	FirstName    string `json:"first_name,omitempty"`
	LastName     string `json:"last_name,omitempty"`
	Address      string `json:"address,omitempty"`
	PostalCode   string `json:"postal_code,omitempty"`
	City         string `json:"city,omitempty"`
	CountryCode  string `json:"country_code,omitempty"`
	Phone        string `json:"phone,omitempty"`
	Email        string `json:"email,omitempty"`
}

// TLSCertificateDownload is an issued certificate as returned by
// DownloadCertificate.
type TLSCertificateDownload struct {
	// Body is the PEM text or the ZIP bundle, depending on the format.
	Body []byte
	// ContentType is the MIME type reported by the API.
	ContentType string
	// Filename is taken from the Content-Disposition header, if present.
	Filename string
}

// ListCertificates lists the TLS certificates of the account.
func (c *Client) ListCertificates(ctx context.Context) ([]TLSCertificate, error) {
	var out []TLSCertificate
	err := c.doJSON(ctx, "GET", "/tls/certificate", nil, &out)
	return out, err
}

// CreateCertificate creates a new TLS certificate order or renews an existing
// certificate.
//
// OV and EV style products can answer with ActionRequired set, in which case
// the order has to be completed with CompleteCertificate.
func (c *Client) CreateCertificate(ctx context.Context, req TLSCertificateRequest) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "POST", "/tls/certificate", req, &out)
	return out, err
}

// GetCertificateByID retrieves a single TLS certificate.
func (c *Client) GetCertificateByID(ctx context.Context, certificateID string) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "GET", "/tls/certificate/"+pathSegment(certificateID), nil, &out)
	return out, err
}

// CompleteCertificate completes an action-required order by binding it to an
// existing usable organization and submitting it to the provider.
func (c *Client) CompleteCertificate(ctx context.Context, certificateID, orgID string) (TLSCertificate, error) {
	var out TLSCertificate
	body := struct {
		OrgID string `json:"org_id"`
	}{OrgID: orgID}
	err := c.doJSON(ctx, "POST", "/tls/certificate/"+pathSegment(certificateID)+"/complete", body, &out)
	return out, err
}

// CancelCertificate cancels a pending TLS certificate order. Use
// CancelIssuedCertificateOrder when an already issued order should be revoked
// in full. req may be nil.
func (c *Client) CancelCertificate(ctx context.Context, certificateID string, req *TLSCertificateCancelRequest) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "POST", "/tls/certificate/"+pathSegment(certificateID)+"/cancel", bodyOrNil(req), &out)
	return out, err
}

// RevokeCertificate revokes only the currently selected certificate while
// keeping the order available for a later reissue. req may be nil.
func (c *Client) RevokeCertificate(ctx context.Context, certificateID string, req *TLSCertificateRevokeRequest) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "POST", "/tls/certificate/"+pathSegment(certificateID)+"/revoke", bodyOrNil(req), &out)
	return out, err
}

// CancelIssuedCertificateOrder cancels a not-yet-issued order, or revokes the
// complete issued order within the first 7 days after ordering. A full
// revocation invalidates all certificate versions of that order and prevents
// further reissues. req may be nil.
func (c *Client) CancelIssuedCertificateOrder(ctx context.Context, certificateID string, req *TLSCertificateOrderCancelRequest) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "POST", "/tls/certificate/"+pathSegment(certificateID)+"/order-cancel", bodyOrNil(req), &out)
	return out, err
}

// ReissueCertificate reissues an already issued or certificate-revoked
// certificate with a new CSR, keeping the same order.
func (c *Client) ReissueCertificate(ctx context.Context, certificateID string, req TLSCertificateReissueRequest) (TLSCertificate, error) {
	var out TLSCertificate
	err := c.doJSON(ctx, "POST", "/tls/certificate/"+pathSegment(certificateID)+"/reissue", req, &out)
	return out, err
}

// DownloadCertificate downloads an issued certificate. format is
// TLSDownloadFormatPEM or TLSDownloadFormatZIP.
func (c *Client) DownloadCertificate(ctx context.Context, certificateID, format string) (TLSCertificateDownload, error) {
	res, err := c.doRaw(ctx, "GET", "/tls/certificate/"+pathSegment(certificateID)+"/download/"+pathSegment(format), nil, nil)
	if err != nil {
		return TLSCertificateDownload{}, err
	}
	return TLSCertificateDownload{
		Body:        res.Body,
		ContentType: res.ContentType,
		Filename:    res.Filename,
	}, nil
}

// ListTLSProducts lists the available certificate products together with the
// current account pricing.
func (c *Client) ListTLSProducts(ctx context.Context) ([]TLSProduct, error) {
	var out []TLSProduct
	err := c.doJSON(ctx, "GET", "/tls/products", nil, &out)
	return out, err
}

// ListOrganizations lists the TLS organizations usable for OV and EV orders.
func (c *Client) ListOrganizations(ctx context.Context) ([]TLSOrganization, error) {
	var out []TLSOrganization
	err := c.doJSON(ctx, "GET", "/tls/organization", nil, &out)
	return out, err
}

// CreateOrganization creates a TLS organization including the local contact
// record used for OV and EV ordering.
func (c *Client) CreateOrganization(ctx context.Context, req TLSOrganizationCreateRequest) (TLSOrganization, error) {
	var out TLSOrganization
	err := c.doJSON(ctx, "POST", "/tls/organization", req, &out)
	return out, err
}

// GetOrganizationByID retrieves a single TLS organization.
func (c *Client) GetOrganizationByID(ctx context.Context, organizationID string) (TLSOrganization, error) {
	var out TLSOrganization
	err := c.doJSON(ctx, "GET", "/tls/organization/"+pathSegment(organizationID), nil, &out)
	return out, err
}

// PatchOrganizationByID updates a TLS organization. Depending on EditMode and
// the changed fields the API may create a replacement organization with a new
// id instead of updating in place.
func (c *Client) PatchOrganizationByID(ctx context.Context, organizationID string, req TLSOrganizationPatchRequest) (TLSOrganization, error) {
	var out TLSOrganization
	err := c.doJSON(ctx, "PATCH", "/tls/organization/"+pathSegment(organizationID), req, &out)
	return out, err
}

// bodyOrNil unwraps an optional request struct so a nil pointer results in a
// request without a body rather than a JSON "null".
func bodyOrNil[T any](req *T) interface{} {
	if req == nil {
		return nil
	}
	return req
}
