package applicant

import (
	"context"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
	"strings"

	"github.com/go-acme/lego/v4/lego"
	"github.com/go-acme/lego/v4/registration"
	"golang.org/x/sync/singleflight"

	"github.com/usual2970/certimate/internal/domain"
	certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
	maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
	"github.com/usual2970/certimate/internal/repository"
)

type acmeUser struct {
	// 证书颁发机构标识。
	// 通常等同于 [CAProviderType] 的值。
	// 对于自定义 ACME CA,值为 "custom#{access_id}"。
	CA string
	// 邮箱。
	Email string
	// 注册信息。
	Registration *registration.Resource

	// CSR 私钥。
	privkey string
}

func newAcmeUser(ca, caAccessId, email string) (*acmeUser, error) {
	repo := repository.NewAcmeAccountRepository()

	applyUser := &acmeUser{
		CA:    ca,
		Email: email,
	}
	if ca == caCustom {
		applyUser.CA = fmt.Sprintf("%s#%s", ca, caAccessId)
	}

	acmeAccount, err := repo.GetByCAAndEmail(applyUser.CA, applyUser.Email)
	if err != nil {
		key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if err != nil {
			return nil, err
		}

		keyPEM, err := certutil.ConvertECPrivateKeyToPEM(key)
		if err != nil {
			return nil, err
		}

		applyUser.privkey = keyPEM
		return applyUser, nil
	}

	applyUser.Registration = acmeAccount.Resource
	applyUser.privkey = acmeAccount.Key

	return applyUser, nil
}

func (u *acmeUser) GetEmail() string {
	return u.Email
}

func (u acmeUser) GetRegistration() *registration.Resource {
	return u.Registration
}

func (u *acmeUser) GetPrivateKey() crypto.PrivateKey {
	rs, _ := certutil.ParseECPrivateKeyFromPEM(u.privkey)
	return rs
}

func (u *acmeUser) hasRegistration() bool {
	return u.Registration != nil
}

func (u *acmeUser) getCAProvider() string {
	return strings.Split(u.CA, "#")[0]
}

func (u *acmeUser) getPrivateKeyPEM() string {
	return u.privkey
}

var registerGroup singleflight.Group

func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) {
	resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", user.CA, user.Email), func() (interface{}, error) {
		return registerAcmeUser(client, user, userRegisterOptions)
	})

	if err != nil {
		return nil, err
	}

	return resp.(*registration.Resource), nil
}

func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) {
	var reg *registration.Resource
	var err error
	switch user.getCAProvider() {
	case caLetsEncrypt, caLetsEncryptStaging:
		reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})

	case caBuypass:
		{
			reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
		}

	case caGoogleTrustServices:
		{
			access := domain.AccessConfigForGoogleTrustServices{}
			if err := maputil.Populate(userRegisterOptions, &access); err != nil {
				return nil, fmt.Errorf("failed to populate provider access config: %w", err)
			}

			reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
				TermsOfServiceAgreed: true,
				Kid:                  access.EabKid,
				HmacEncoded:          access.EabHmacKey,
			})
		}

	case caSSLCom:
		{
			access := domain.AccessConfigForSSLCom{}
			if err := maputil.Populate(userRegisterOptions, &access); err != nil {
				return nil, fmt.Errorf("failed to populate provider access config: %w", err)
			}

			reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
				TermsOfServiceAgreed: true,
				Kid:                  access.EabKid,
				HmacEncoded:          access.EabHmacKey,
			})
		}

	case caZeroSSL:
		{
			access := domain.AccessConfigForZeroSSL{}
			if err := maputil.Populate(userRegisterOptions, &access); err != nil {
				return nil, fmt.Errorf("failed to populate provider access config: %w", err)
			}

			reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
				TermsOfServiceAgreed: true,
				Kid:                  access.EabKid,
				HmacEncoded:          access.EabHmacKey,
			})
		}

	case caCustom:
		{
			access := domain.AccessConfigForACMECA{}
			if err := maputil.Populate(userRegisterOptions, &access); err != nil {
				return nil, fmt.Errorf("failed to populate provider access config: %w", err)
			}

			if access.EabKid == "" && access.EabHmacKey == "" {
				reg, err = client.Registration.Register(registration.RegisterOptions{
					TermsOfServiceAgreed: true,
				})
			} else {
				reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
					TermsOfServiceAgreed: true,
					Kid:                  access.EabKid,
					HmacEncoded:          access.EabHmacKey,
				})
			}
		}

	default:
		err = fmt.Errorf("unsupported ca provider '%s'", user.CA)
	}
	if err != nil {
		return nil, err
	}

	repo := repository.NewAcmeAccountRepository()
	resp, err := repo.GetByCAAndEmail(user.CA, user.Email)
	if err == nil {
		user.privkey = resp.Key
		return resp.Resource, nil
	}

	if _, err := repo.Save(context.Background(), &domain.AcmeAccount{
		CA:       user.CA,
		Email:    user.Email,
		Key:      user.getPrivateKeyPEM(),
		Resource: reg,
	}); err != nil {
		return nil, fmt.Errorf("failed to save acme account registration: %w", err)
	}

	return reg, nil
}