package payment

import (
	"crypto/x509"
	"encoding/asn1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"
)

const (
	maxAppleJWSHeaderSize  = 256 << 10
	maxAppleJWSPayloadSize = 1 << 20
	maxAppleJWSTimeDepth   = 2
)

var (
	appleJWSLeafExtensionOID         = asn1.ObjectIdentifier{1, 2, 840, 113635, 100, 6, 11, 1}
	appleJWSIntermediateExtensionOID = asn1.ObjectIdentifier{1, 2, 840, 113635, 100, 6, 2, 1}
)

// validateAppleJWSChain binds the leaf certificate used by GoPay for JWS
// signature verification to the intermediate and root certificates that
// GoPay validates against its embedded Apple trust anchor.
func validateAppleJWSChain(signedPayload string) error {
	headerSegment, payloadSegment, signatureSegment, err := splitAppleJWS(signedPayload)
	if err != nil {
		return err
	}
	verificationTime, hasVerificationTime, err := appleJWSVerificationTime(payloadSegment)
	if err != nil {
		return err
	}
	if signatureSegment == "" {
		return errors.New("Apple JWS 格式无效")
	}
	if len(headerSegment) > base64.RawURLEncoding.EncodedLen(maxAppleJWSHeaderSize) {
		return errors.New("Apple JWS header 过大")
	}
	headerBytes, err := base64.RawURLEncoding.DecodeString(headerSegment)
	if err != nil {
		return fmt.Errorf("解析 Apple JWS header: %w", err)
	}
	if len(headerBytes) > maxAppleJWSHeaderSize {
		return errors.New("Apple JWS header 过大")
	}
	var header struct {
		Algorithm string   `json:"alg"`
		Chain     []string `json:"x5c"`
	}
	if err = json.Unmarshal(headerBytes, &header); err != nil {
		return fmt.Errorf("解析 Apple JWS header: %w", err)
	}
	if header.Algorithm != "ES256" {
		return errors.New("Apple JWS 算法必须是 ES256")
	}
	if len(header.Chain) != 3 {
		return errors.New("Apple JWS x5c 必须包含叶子、中间和根证书")
	}

	certificates := make([]*x509.Certificate, 0, len(header.Chain))
	for index, encoded := range header.Chain {
		der, decodeErr := base64.StdEncoding.DecodeString(encoded)
		if decodeErr != nil {
			return fmt.Errorf("解析 Apple JWS x5c[%d]: %w", index, decodeErr)
		}
		certificate, parseErr := x509.ParseCertificate(der)
		if parseErr != nil {
			return fmt.Errorf("解析 Apple JWS x5c[%d] 证书: %w", index, parseErr)
		}
		certificates = append(certificates, certificate)
	}
	if err = certificates[0].CheckSignatureFrom(certificates[1]); err != nil {
		return fmt.Errorf("Apple JWS 叶子证书不属于声明的证书链: %w", err)
	}
	if err = certificates[1].CheckSignatureFrom(certificates[2]); err != nil {
		return fmt.Errorf("Apple JWS 中间证书不属于声明的证书链: %w", err)
	}
	if err = certificates[2].CheckSignatureFrom(certificates[2]); err != nil {
		return fmt.Errorf("Apple JWS 根证书不是自签名证书: %w", err)
	}
	if !certificates[1].IsCA || !certificates[1].BasicConstraintsValid {
		return errors.New("Apple JWS 中间证书不是有效 CA")
	}
	if !certificates[2].IsCA || !certificates[2].BasicConstraintsValid {
		return errors.New("Apple JWS 根证书不是有效 CA")
	}
	if certificates[1].KeyUsage != 0 && certificates[1].KeyUsage&x509.KeyUsageCertSign == 0 {
		return errors.New("Apple JWS 中间证书不允许签发证书")
	}
	if certificates[2].KeyUsage != 0 && certificates[2].KeyUsage&x509.KeyUsageCertSign == 0 {
		return errors.New("Apple JWS 根证书不允许签发证书")
	}
	if !hasAppleCertificateExtension(certificates[0], appleJWSLeafExtensionOID) {
		return errors.New("Apple JWS 叶子证书缺少 App Store 签名扩展")
	}
	if !hasAppleCertificateExtension(certificates[1], appleJWSIntermediateExtensionOID) {
		return errors.New("Apple JWS 中间证书缺少 Apple 签名扩展")
	}

	if hasVerificationTime {
		roots := x509.NewCertPool()
		roots.AddCert(certificates[2])
		intermediates := x509.NewCertPool()
		intermediates.AddCert(certificates[1])
		if _, err = certificates[0].Verify(x509.VerifyOptions{
			Roots:         roots,
			Intermediates: intermediates,
			KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
			CurrentTime:   verificationTime,
		}); err != nil {
			return fmt.Errorf("验证 Apple JWS 证书链: %w", err)
		}
	}
	if certificates[0].IsCA {
		return errors.New("Apple JWS 叶子证书不能是 CA")
	}
	if certificates[0].KeyUsage != 0 && certificates[0].KeyUsage&x509.KeyUsageDigitalSignature == 0 {
		return errors.New("Apple JWS 叶子证书不允许数字签名")
	}
	return nil
}

func splitAppleJWS(signedPayload string) (header, payload, signature string, err error) {
	header, remainder, ok := strings.Cut(strings.TrimSpace(signedPayload), ".")
	if !ok || header == "" {
		return "", "", "", errors.New("Apple JWS 格式无效")
	}
	payload, signature, ok = strings.Cut(remainder, ".")
	if !ok || payload == "" || signature == "" || strings.Contains(signature, ".") {
		return "", "", "", errors.New("Apple JWS 格式无效")
	}
	return header, payload, signature, nil
}

func appleJWSVerificationTime(payloadSegment string) (time.Time, bool, error) {
	return appleJWSVerificationTimeAtDepth(payloadSegment, 0)
}

// appleJWSVerificationTimeAtDepth extracts a signing time from the current
// payload, then follows the nested signed transaction/renewal JWS used by
// App Store Server Notifications. The outer notification often has no date of
// its own, while its nested transaction does.
func appleJWSVerificationTimeAtDepth(payloadSegment string, depth int) (time.Time, bool, error) {
	if len(payloadSegment) > base64.RawURLEncoding.EncodedLen(maxAppleJWSPayloadSize) {
		return time.Time{}, false, errors.New("Apple JWS payload 过大")
	}
	payloadBytes, err := base64.RawURLEncoding.DecodeString(payloadSegment)
	if err != nil {
		return time.Time{}, false, fmt.Errorf("解析 Apple JWS payload: %w", err)
	}
	if len(payloadBytes) > maxAppleJWSPayloadSize {
		return time.Time{}, false, errors.New("Apple JWS payload 过大")
	}
	var payload struct {
		SignedDate          json.Number `json:"signedDate"`
		ReceiptCreationDate json.Number `json:"receiptCreationDate"`
		Data                struct {
			SignedTransactionInfo string `json:"signedTransactionInfo"`
			SignedRenewalInfo     string `json:"signedRenewalInfo"`
		} `json:"data"`
	}
	if err = json.Unmarshal(payloadBytes, &payload); err != nil {
		return time.Time{}, false, fmt.Errorf("解析 Apple JWS payload: %w", err)
	}
	date := payload.SignedDate
	if date == "" {
		date = payload.ReceiptCreationDate
	}
	if date != "" {
		milliseconds, parseErr := date.Int64()
		if parseErr != nil || milliseconds <= 0 {
			return time.Time{}, false, errors.New("Apple JWS signedDate 无效")
		}
		return time.UnixMilli(milliseconds), true, nil
	}
	if depth >= maxAppleJWSTimeDepth {
		return time.Time{}, false, nil
	}
	for _, nested := range []string{payload.Data.SignedTransactionInfo, payload.Data.SignedRenewalInfo} {
		if strings.TrimSpace(nested) == "" {
			continue
		}
		_, nestedPayload, _, splitErr := splitAppleJWS(nested)
		if splitErr != nil {
			continue
		}
		if nestedTime, found, nestedErr := appleJWSVerificationTimeAtDepth(nestedPayload, depth+1); nestedErr != nil {
			return time.Time{}, false, nestedErr
		} else if found {
			return nestedTime, true, nil
		}
	}
	return time.Time{}, false, nil
}

func hasAppleCertificateExtension(certificate *x509.Certificate, oid asn1.ObjectIdentifier) bool {
	if certificate == nil {
		return false
	}
	for _, extension := range certificate.Extensions {
		if extension.Id.Equal(oid) {
			return true
		}
	}
	return false
}
