// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package acme

import (
	"crypto"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// ACME status values of Account, Order, Authorization and Challenge objects.
// See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details.
const (
	StatusDeactivated = "deactivated"
	StatusExpired     = "expired"
	StatusInvalid     = "invalid"
	StatusPending     = "pending"
	StatusProcessing  = "processing"
	StatusReady       = "ready"
	StatusRevoked     = "revoked"
	StatusUnknown     = "unknown"
	StatusValid       = "valid"
)

// CRLReasonCode identifies the reason for a certificate revocation.
type CRLReasonCode int

// CRL reason codes as defined in RFC 5280.
const (
	CRLReasonUnspecified          CRLReasonCode = 0
	CRLReasonKeyCompromise        CRLReasonCode = 1
	CRLReasonCACompromise         CRLReasonCode = 2
	CRLReasonAffiliationChanged   CRLReasonCode = 3
	CRLReasonSuperseded           CRLReasonCode = 4
	CRLReasonCessationOfOperation CRLReasonCode = 5
	CRLReasonCertificateHold      CRLReasonCode = 6
	CRLReasonRemoveFromCRL        CRLReasonCode = 8
	CRLReasonPrivilegeWithdrawn   CRLReasonCode = 9
	CRLReasonAACompromise         CRLReasonCode = 10
)

var (
	// ErrUnsupportedKey is returned when an unsupported key type is encountered.
	ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")

	// ErrAccountAlreadyExists indicates that the Client's key has already been registered
	// with the CA. It is returned by Register method.
	ErrAccountAlreadyExists = errors.New("acme: account already exists")

	// ErrNoAccount indicates that the Client's key has not been registered with the CA.
	ErrNoAccount = errors.New("acme: account does not exist")

	// errPreAuthorizationNotSupported indicates that the server does not
	// support pre-authorization of identifiers.
	errPreAuthorizationNotSupported = errors.New("acme: pre-authorization is not supported")

	// ErrCADoesNotSupportProfiles indicates that [WithOrderProfile] was
	// included with a CA that does not advertise support for profiles in
	// their directory.
	ErrCADoesNotSupportProfiles = errors.New("acme: certificate authority does not support profiles")

	// ErrProfileNotInSetOfSupportedProfiles indicates that the profile
	// specified with [WithOrderProfile} is not one supported by the CA
	ErrProfileNotInSetOfSupportedProfiles = errors.New("acme: certificate authority does not advertise a profile with name")
)

// A Subproblem describes an ACME subproblem as reported in an Error.
type Subproblem struct {
	// Type is a URI reference that identifies the problem type,
	// typically in a "urn:acme:error:xxx" form.
	Type string
	// Detail is a human-readable explanation specific to this occurrence of the problem.
	Detail string
	// Instance indicates a URL that the client should direct a human user to visit
	// in order for instructions on how to agree to the updated Terms of Service.
	// In such an event CA sets StatusCode to 403, Type to
	// "urn:ietf:params:acme:error:userActionRequired", and adds a Link header with relation
	// "terms-of-service" containing the latest TOS URL.
	Instance string
	// Identifier may contain the ACME identifier that the error is for.
	Identifier *AuthzID
}

func (sp Subproblem) String() string {
	str := fmt.Sprintf("%s: ", sp.Type)
	if sp.Identifier != nil {
		str += fmt.Sprintf("[%s: %s] ", sp.Identifier.Type, sp.Identifier.Value)
	}
	str += sp.Detail
	return str
}

// Error is an ACME error, defined in Problem Details for HTTP APIs doc
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
type Error struct {
	// StatusCode is The HTTP status code generated by the origin server.
	StatusCode int
	// ProblemType is a URI reference that identifies the problem type,
	// typically in a "urn:acme:error:xxx" form.
	ProblemType string
	// Detail is a human-readable explanation specific to this occurrence of the problem.
	Detail string
	// Instance indicates a URL that the client should direct a human user to visit
	// in order for instructions on how to agree to the updated Terms of Service.
	// In such an event CA sets StatusCode to 403, ProblemType to
	// "urn:ietf:params:acme:error:userActionRequired" and a Link header with relation
	// "terms-of-service" containing the latest TOS URL.
	Instance string
	// Header is the original server error response headers.
	// It may be nil.
	Header http.Header
	// Subproblems may contain more detailed information about the individual problems
	// that caused the error. This field is only sent by RFC 8555 compatible ACME
	// servers. Defined in RFC 8555 Section 6.7.1.
	Subproblems []Subproblem
}

func (e *Error) Error() string {
	str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
	if len(e.Subproblems) > 0 {
		str += fmt.Sprintf("; subproblems:")
		for _, sp := range e.Subproblems {
			str += fmt.Sprintf("\n\t%s", sp)
		}
	}
	return str
}

// AuthorizationError indicates that an authorization for an identifier
// did not succeed.
// It contains all errors from Challenge items of the failed Authorization.
type AuthorizationError struct {
	// URI uniquely identifies the failed Authorization.
	URI string

	// Identifier is an AuthzID.Value of the failed Authorization.
	Identifier string

	// Errors is a collection of non-nil error values of Challenge items
	// of the failed Authorization.
	Errors []error
}

func (a *AuthorizationError) Error() string {
	e := make([]string, len(a.Errors))
	for i, err := range a.Errors {
		e[i] = err.Error()
	}

	if a.Identifier != "" {
		return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
	}

	return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; "))
}

// OrderError is returned from Client's order related methods.
// It indicates the order is unusable and the clients should start over with
// AuthorizeOrder.
//
// The clients can still fetch the order object from CA using GetOrder
// to inspect its state.
type OrderError struct {
	OrderURL string
	Status   string
}

func (oe *OrderError) Error() string {
	return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
}

// RateLimit reports whether err represents a rate limit error and
// any Retry-After duration returned by the server.
//
// See the following for more details on rate limiting:
// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
func RateLimit(err error) (time.Duration, bool) {
	e, ok := err.(*Error)
	if !ok {
		return 0, false
	}
	// Some CA implementations may return incorrect values.
	// Use case-insensitive comparison.
	if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
		return 0, false
	}
	if e.Header == nil {
		return 0, true
	}
	return retryAfter(e.Header.Get("Retry-After")), true
}

// Account is a user account. It is associated with a private key.
// Non-RFC 8555 fields are empty when interfacing with a compliant CA.
type Account struct {
	// URI is the account unique ID, which is also a URL used to retrieve
	// account data from the CA.
	// When interfacing with RFC 8555-compliant CAs, URI is the "kid" field
	// value in JWS signed requests.
	URI string

	// Contact is a slice of contact info used during registration.
	// See https://tools.ietf.org/html/rfc8555#section-7.3 for supported
	// formats.
	Contact []string

	// Status indicates current account status as returned by the CA.
	// Possible values are StatusValid, StatusDeactivated, and StatusRevoked.
	Status string

	// OrdersURL is a URL from which a list of orders submitted by this account
	// can be fetched.
	OrdersURL string

	// The terms user has agreed to.
	// A value not matching CurrentTerms indicates that the user hasn't agreed
	// to the actual Terms of Service of the CA.
	//
	// It is non-RFC 8555 compliant. Package users can store the ToS they agree to
	// during Client's Register call in the prompt callback function.
	AgreedTerms string

	// Actual terms of a CA.
	//
	// It is non-RFC 8555 compliant. Use Directory's Terms field.
	// When a CA updates their terms and requires an account agreement,
	// a URL at which instructions to do so is available in Error's Instance field.
	CurrentTerms string

	// Authz is the authorization URL used to initiate a new authz flow.
	//
	// It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL.
	Authz string

	// Authorizations is a URI from which a list of authorizations
	// granted to this account can be fetched via a GET request.
	//
	// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
	Authorizations string

	// Certificates is a URI from which a list of certificates
	// issued for this account can be fetched via a GET request.
	//
	// It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
	Certificates string

	// ExternalAccountBinding represents an arbitrary binding to an account of
	// the CA which the ACME server is tied to.
	// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
	ExternalAccountBinding *ExternalAccountBinding
}

// ExternalAccountBinding contains the data needed to form a request with
// an external account binding.
// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
type ExternalAccountBinding struct {
	// KID is the Key ID of the symmetric MAC key that the CA provides to
	// identify an external account from ACME.
	KID string

	// Key is the bytes of the symmetric key that the CA provides to identify
	// the account. Key must correspond to the KID.
	Key []byte
}

func (e *ExternalAccountBinding) String() string {
	return fmt.Sprintf("&{KID: %q, Key: redacted}", e.KID)
}

// Directory is ACME server discovery data.
// See https://tools.ietf.org/html/rfc8555#section-7.1.1 for more details.
type Directory struct {
	// NonceURL indicates an endpoint where to fetch fresh nonce values from.
	NonceURL string

	// RegURL is an account endpoint URL, allowing for creating new accounts.
	// Pre-RFC 8555 CAs also allow modifying existing accounts at this URL.
	RegURL string

	// OrderURL is used to initiate the certificate issuance flow
	// as described in RFC 8555.
	OrderURL string

	// AuthzURL is used to initiate identifier pre-authorization flow.
	// Empty string indicates the flow is unsupported by the CA.
	AuthzURL string

	// CertURL is a new certificate issuance endpoint URL.
	// It is non-RFC 8555 compliant and is obsoleted by OrderURL.
	CertURL string

	// RevokeURL is used to initiate a certificate revocation flow.
	RevokeURL string

	// KeyChangeURL allows to perform account key rollover flow.
	KeyChangeURL string

	// Terms is a URI identifying the current terms of service.
	Terms string

	// Website is an HTTP or HTTPS URL locating a website
	// providing more information about the ACME server.
	Website string

	// CAA consists of lowercase hostname elements, which the ACME server
	// recognises as referring to itself for the purposes of CAA record validation
	// as defined in RFC 6844.
	CAA []string

	// ExternalAccountRequired indicates that the CA requires for all account-related
	// requests to include external account binding information.
	ExternalAccountRequired bool

	// Profiles indicates that the CA supports specifying a profile for an
	// order. See also [WithOrderNotAfter].
	Profiles Profiles
}

// Order represents a client's request for a certificate.
// It tracks the request flow progress through to issuance.
type Order struct {
	// URI uniquely identifies an order.
	URI string

	// Status represents the current status of the order.
	// It indicates which action the client should take.
	//
	// Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid.
	// Pending means the CA does not believe that the client has fulfilled the requirements.
	// Ready indicates that the client has fulfilled all the requirements and can submit a CSR
	// to obtain a certificate. This is done with Client's CreateOrderCert.
	// Processing means the certificate is being issued.
	// Valid indicates the CA has issued the certificate. It can be downloaded
	// from the Order's CertURL. This is done with Client's FetchCert.
	// Invalid means the certificate will not be issued. Users should consider this order
	// abandoned.
	Status string

	// Expires is the timestamp after which CA considers this order invalid.
	Expires time.Time

	// Identifiers contains all identifier objects which the order pertains to.
	Identifiers []AuthzID

	// NotBefore is the requested value of the notBefore field in the certificate.
	NotBefore time.Time

	// NotAfter is the requested value of the notAfter field in the certificate.
	NotAfter time.Time

	// AuthzURLs represents authorizations to complete before a certificate
	// for identifiers specified in the order can be issued.
	// It also contains unexpired authorizations that the client has completed
	// in the past.
	//
	// Authorization objects can be fetched using Client's GetAuthorization method.
	//
	// The required authorizations are dictated by CA policies.
	// There may not be a 1:1 relationship between the identifiers and required authorizations.
	// Required authorizations can be identified by their StatusPending status.
	//
	// For orders in the StatusValid or StatusInvalid state these are the authorizations
	// which were completed.
	AuthzURLs []string

	// FinalizeURL is the endpoint at which a CSR is submitted to obtain a certificate
	// once all the authorizations are satisfied.
	FinalizeURL string

	// CertURL points to the certificate that has been issued in response to this order.
	CertURL string

	// The error that occurred while processing the order as received from a CA, if any.
	Error *Error
}

// OrderOption allows customizing Client.AuthorizeOrder call.
type OrderOption interface {
	privateOrderOpt()
}

// WithOrderNotBefore sets order's NotBefore field.
func WithOrderNotBefore(t time.Time) OrderOption {
	return orderNotBeforeOpt(t)
}

// WithOrderNotAfter sets order's NotAfter field.
func WithOrderNotAfter(t time.Time) OrderOption {
	return orderNotAfterOpt(t)
}

// WithOrderProfile sets an order's Profile field for servers which support
// profiles.
// See also:
// * https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
// * https://letsencrypt.org/docs/profiles/
func WithOrderProfile(name string) OrderOption {
	return orderProfileOpt(name)
}

type orderNotBeforeOpt time.Time

func (orderNotBeforeOpt) privateOrderOpt() {}

type orderNotAfterOpt time.Time

func (orderNotAfterOpt) privateOrderOpt() {}

type orderProfileOpt string

func (orderProfileOpt) privateOrderOpt() {}

// Authorization encodes an authorization response.
type Authorization struct {
	// URI uniquely identifies a authorization.
	URI string

	// Status is the current status of an authorization.
	// Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated,
	// StatusExpired and StatusRevoked.
	Status string

	// Identifier is what the account is authorized to represent.
	Identifier AuthzID

	// The timestamp after which the CA considers the authorization invalid.
	Expires time.Time

	// Wildcard is true for authorizations of a wildcard domain name.
	Wildcard bool

	// Challenges that the client needs to fulfill in order to prove possession
	// of the identifier (for pending authorizations).
	// For valid authorizations, the challenge that was validated.
	// For invalid authorizations, the challenge that was attempted and failed.
	//
	// RFC 8555 compatible CAs require users to fuflfill only one of the challenges.
	Challenges []*Challenge

	// A collection of sets of challenges, each of which would be sufficient
	// to prove possession of the identifier.
	// Clients must complete a set of challenges that covers at least one set.
	// Challenges are identified by their indices in the challenges array.
	// If this field is empty, the client needs to complete all challenges.
	//
	// This field is unused in RFC 8555.
	Combinations [][]int
}

// AuthzID is an identifier that an account is authorized to represent.
type AuthzID struct {
	Type  string // The type of identifier, "dns" or "ip".
	Value string // The identifier itself, e.g. "example.org".
}

// DomainIDs creates a slice of AuthzID with "dns" identifier type.
func DomainIDs(names ...string) []AuthzID {
	a := make([]AuthzID, len(names))
	for i, v := range names {
		a[i] = AuthzID{Type: "dns", Value: v}
	}
	return a
}

// IPIDs creates a slice of AuthzID with "ip" identifier type.
// Each element of addr is textual form of an address as defined
// in RFC 1123 Section 2.1 for IPv4 and in RFC 5952 Section 4 for IPv6.
func IPIDs(addr ...string) []AuthzID {
	a := make([]AuthzID, len(addr))
	for i, v := range addr {
		a[i] = AuthzID{Type: "ip", Value: v}
	}
	return a
}

// wireAuthzID is ACME JSON representation of authorization identifier objects.
type wireAuthzID struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

// wireAuthz is ACME JSON representation of Authorization objects.
type wireAuthz struct {
	Identifier   wireAuthzID
	Status       string
	Expires      time.Time
	Wildcard     bool
	Challenges   []wireChallenge
	Combinations [][]int
	Error        *wireError
}

func (z *wireAuthz) authorization(uri string) *Authorization {
	a := &Authorization{
		URI:          uri,
		Status:       z.Status,
		Identifier:   AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
		Expires:      z.Expires,
		Wildcard:     z.Wildcard,
		Challenges:   make([]*Challenge, len(z.Challenges)),
		Combinations: z.Combinations, // shallow copy
	}
	for i, v := range z.Challenges {
		a.Challenges[i] = v.challenge()
	}
	return a
}

func (z *wireAuthz) error(uri string) *AuthorizationError {
	err := &AuthorizationError{
		URI:        uri,
		Identifier: z.Identifier.Value,
	}

	if z.Error != nil {
		err.Errors = append(err.Errors, z.Error.error(nil))
	}

	for _, raw := range z.Challenges {
		if raw.Error != nil {
			err.Errors = append(err.Errors, raw.Error.error(nil))
		}
	}

	return err
}

// Challenge encodes a returned CA challenge.
// Its Error field may be non-nil if the challenge is part of an Authorization
// with StatusInvalid.
type Challenge struct {
	// Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01".
	Type string

	// URI is where a challenge response can be posted to.
	URI string

	// Token is a random value that uniquely identifies the challenge.
	Token string

	// Status identifies the status of this challenge.
	// In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid,
	// and StatusInvalid.
	Status string

	// Validated is the time at which the CA validated this challenge.
	// Always zero value in pre-RFC 8555.
	Validated time.Time

	// Error indicates the reason for an authorization failure
	// when this challenge was used.
	// The type of a non-nil value is *Error.
	Error error

	// Payload is the JSON-formatted payload that the client sends
	// to the server to indicate it is ready to respond to the challenge.
	// When unset, it defaults to an empty JSON object: {}.
	// For most challenges, the client must not set Payload,
	// see https://tools.ietf.org/html/rfc8555#section-7.5.1.
	// Payload is used only for newer challenges (such as "device-attest-01")
	// where the client must send additional data for the server to validate
	// the challenge.
	Payload json.RawMessage
}

// wireChallenge is ACME JSON challenge representation.
type wireChallenge struct {
	URL       string `json:"url"` // RFC
	URI       string `json:"uri"` // pre-RFC
	Type      string
	Token     string
	Status    string
	Validated time.Time
	Error     *wireError
}

func (c *wireChallenge) challenge() *Challenge {
	v := &Challenge{
		URI:    c.URL,
		Type:   c.Type,
		Token:  c.Token,
		Status: c.Status,
	}
	if v.URI == "" {
		v.URI = c.URI // c.URL was empty; use legacy
	}
	if v.Status == "" {
		v.Status = StatusPending
	}
	if c.Error != nil {
		v.Error = c.Error.error(nil)
	}
	return v
}

// wireError is a subset of fields of the Problem Details object
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
type wireError struct {
	Status      int
	Type        string
	Detail      string
	Instance    string
	Subproblems []Subproblem
}

func (e *wireError) error(h http.Header) *Error {
	err := &Error{
		StatusCode:  e.Status,
		ProblemType: e.Type,
		Detail:      e.Detail,
		Instance:    e.Instance,
		Header:      h,
		Subproblems: e.Subproblems,
	}
	return err
}

// CertOption is an optional argument type for the TLS ChallengeCert methods for
// customizing a temporary certificate for TLS-based challenges.
type CertOption interface {
	privateCertOpt()
}

// WithKey creates an option holding a private/public key pair.
// The private part signs a certificate, and the public part represents the signee.
func WithKey(key crypto.Signer) CertOption {
	return &certOptKey{key}
}

type certOptKey struct {
	key crypto.Signer
}

func (*certOptKey) privateCertOpt() {}

// WithTemplate creates an option for specifying a certificate template.
// See x509.CreateCertificate for template usage details.
//
// In TLS ChallengeCert methods, the template is also used as parent,
// resulting in a self-signed certificate.
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
func WithTemplate(t *x509.Certificate) CertOption {
	return (*certOptTemplate)(t)
}

type certOptTemplate x509.Certificate

func (*certOptTemplate) privateCertOpt() {}

type Profiles map[string]string

func (ps Profiles) isSupported() bool {
	return len(ps) > 0
}

func (ps Profiles) GetDescription(name string) string {
	return ps[name]
}

func (ps Profiles) Has(name string) bool {
	_, ok := ps[name]
	return ok
}
